[英]Haskell: Parsing an object that could be multiple types into one single type
I'm a haskell beginner going through aeson, learning more about both by parsing some data files.我是一个使用 aeson 的 Haskell 初学者,通过解析一些数据文件来了解更多关于两者的信息。
Usually when there's a data file, may it be .json
, a lua
table, .csv
format or others, and you want to parse them, there's always a chance of error.通常当有数据文件时,可能是.json
、 lua
表、 .csv
格式或其他格式,你想解析它们,总是有出错的机会。
For example, a simple .json
file like this例如,像这样一个简单的.json
文件
"root": {
"m1": {
"key1": "value1",
"key2": 2
},
"m2": {
"key1": 1
},
}
Has two oddities: "m1"
has two subkeys, one has a value in String
and one in Int
.有两个奇怪的地方: "m1"
有两个子键,一个在String
有一个值,另一个在Int
。 "m2"
has only one subkey, and it has same key as the one above it but the value has a different type ie. "m2"
只有一个子键,它和它上面的键有相同的键,但值有不同的类型,即。 Int
. Int
。
If it were like this如果是这样
"root": {
"m1": {
"key1": "value1",
"key2": 2
},
"m2": {
"key1": "value1",
"key2": 2
},
}
A simple way of parsing it with Aeson would be with these datatypes使用 Aeson 解析它的一种简单方法是使用这些数据类型
data Root = Root { Map String Key
} deriving (Show, Generic)
data Key = Key { key1 :: String
, key2 :: Int
} deriving (Show, Generic)
If a key was missing如果缺少钥匙
"root": {
"m1": {
"key1": "value1",
"key2": 2
},
"m2": {
"key1": "value1"
},
}
This could have done the job这本可以完成这项工作
data Root = Root { Map String Key
} deriving (Show, Generic)
data Key = Key { key1 :: String
, key2 :: Maybe Int
} deriving (Show, Generic)
But what if it were like the first example where not only can the keys not have a value but also have completely different ones.但是,如果它就像第一个示例一样,其中键不仅可以没有值,而且还可以具有完全不同的值。
What if in them you only cared about the numbers or the strings?如果在它们中你只关心数字或字符串怎么办? Would there be a way of parsing them without going out of the type definitions?有没有一种方法可以在不脱离类型定义的情况下解析它们?
Going through some quick searches I found out the Alternative class is just meant for this kind of problems and operator like *>
, <>
, <|>
can prove useful, but I'm not sure how.经过一些快速搜索,我发现 Alternative 类仅适用于此类问题,并且像*>
、 <>
、 <|>
这样的运算符可以证明很有用,但我不确定如何。
I know I need to define a type that can encapsulate all three chances if I just wanted the text or numbers, like我知道如果我只想要文本或数字,我需要定义一个可以封装所有三个机会的类型,比如
Data NeededVal = NoValue | TextValue | Needed Int
or或者
Data NeededVal = NoValue | NumericValue | Needed String
but I'm not sure how I'd go about making them an instance of Applicative & Alternative so that the idea would work out.但我不确定如何让它们成为 Applicative & Alternative 的实例,以便这个想法能够实现。
This is a short follow-up of my previous question这是我上一个问题的简短后续
Well, I try to play with the JSON as below:好吧,我尝试使用 JSON,如下所示:
"root": {
"m1": {
"key1": "value1",
"key2": 2
},
"m2": {
"key1": 1
},
}
and parse it to the follow data types using Data.Aeson :并使用Data.Aeson将其解析为以下数据类型:
data Root = Root (Map String Key) deriving (Show)
data NeededVal = NoValue | NumericValue | Needed String deriving (Show)
data Key = Key { key1 :: NeededVal , key2 :: NeededVal } deriving (Show)
To handle NoValue
, I use Alternative <|>
as为了处理NoValue
,我使用 Alternative <|>
作为
instance FromJSON Key where
parseJSON = withObject "Key" $ \obj -> do
k1 <- obj .: (pack "key1") <|> pure NoValue
k2 <- obj .: (pack "key2") <|> pure NoValue
return(Key k1 k2)
To test String
and numeric
type, I use Value
constructor as:为了测试String
和numeric
类型,我使用Value
构造函数作为:
instance FromJSON NeededVal where
parseJSON (String txt) = return $ Needed $ unpack txt
parseJSON (Number _) = return $ NumericValue
parseJSON _ = return NoValue
To skip m1
and m2
objects and read the keys
value immediately as:跳过m1
和m2
对象并立即读取keys
,如下所示:
import Data.Map as Map (Map, fromList)
import Data.HashMap.Strict as HM (toList, lookup)
import Data.Aeson.Types (Parser)
parseJSON = withObject "Root"
$ \rootObj-> case HM.lookup (pack "root") rootObj of
Nothing -> fail "no Root"
Just val -> withObject "Key List" mkRoot val
where mkRoot obj =
let (ks, vs) = unzip $ HM.toList obj
ks' = map unpack ks
in do vs' <- mapM parseJSON vs::Parser [Key]
return $ Root $ Map.fromList $ zip ks' vs'
and the final result:和最终结果:
Right (Root (fromList [
("m1",Key {key1 = Needed "value1", key2 = NumericValue}),
("m2",Key {key1 = NumericValue, key2 = NoValue})]
))
Side notes:旁注:
but I'm not sure how I'd go about making them an instance of Applicative & Alternative so that the idea would work out.但我不确定如何让它们成为 Applicative & Alternative 的实例,以便这个想法能够实现。
No , No need to make them as an instance of Applicative and Alternative
, the <|>
operator apply on Parser
(defined in Data.Aeson.Types
) not the user defined data type.不,不需要将它们作为Applicative and Alternative
的实例, <|>
运算符应用于Parser
(在Data.Aeson.Types
定义)而不是用户定义的数据类型。 Parser
has already be an instance of Alternative
. Parser
已经是Alternative
一个实例。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.