Haskell:将一个可能是多个类型的对象解析为一个类型



我是haskell的初学者,正在学习aeson,通过解析一些数据文件了解更多关于这两者的信息。

通常,当有一个数据文件时,可能是.jsonlua表、.csv格式或其他格式,并且您想要解析它们时,总是有出错的可能性。

例如,像这样的简单.json文件

"root": {
"m1": {
"key1": "value1",
"key2": 2
},
"m2": {
"key1": 1
},
}

有两个奇数:"m1"有两个子键,一个子键在String中有值,一个子密钥在Int中有值。"m2"只有一个子键,它和上面的键相同,但值有不同的类型,即Int


如果是这样的

"root": {
"m1": {
"key1": "value1",
"key2": 2
},
"m2": {
"key1": "value1",
"key2": 2 
},
}

用Aeson解析它的一个简单方法是使用这些数据类型

data Root = Root { Map String Key
} deriving (Show, Generic)
data Key = Key { key1 :: String
, key2 :: Int
} deriving (Show, Generic)

如果密钥丢失

"root": {
"m1": {
"key1": "value1",
"key2": 2
},
"m2": {
"key1": "value1"
},
}

这本可以完成的工作

data Root = Root { Map String Key
} deriving (Show, Generic)
data Key = Key { key1 :: String
, key2 :: Maybe Int
} deriving (Show, Generic)

但如果它像第一个例子一样,键不仅不能有值,而且可以有完全不同的值呢。

如果你只关心数字或字符串呢?有没有一种方法可以在不脱离类型定义的情况下解析它们?

通过一些快速搜索,我发现Alternative类只是针对这类问题的,像*><><|>这样的运算符可以证明是有用的,但我不确定如何使用。

我知道如果我只是想要文本或数字,我需要定义一个可以封装所有三种机会的类型,比如

Data NeededVal = NoValue | TextValue | Needed Int

Data NeededVal = NoValue | NumericValue | Needed String

但我不确定我会如何让它们成为Applicative&备选方案,以便想法能够实现。

这是我之前问题的简短后续

好吧,我试着玩下面的JSON:

"root": {
"m1": {
"key1": "value1",
"key2": 2
},
"m2": {
"key1": 1
},
}

并使用数据将其解析为以下数据类型。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)

为了处理NoValue,我使用备选<|>作为

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)

为了测试Stringnumeric类型,我使用Value构造函数作为:

instance FromJSON NeededVal where
parseJSON (String txt) = return $ Needed $ unpack txt
parseJSON (Number _)   = return $ NumericValue
parseJSON _            = return NoValue

跳过m1m2对象并立即将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'

最终结果:

Right (Root (fromList [
("m1",Key {key1 = Needed "value1", key2 = NumericValue}),
("m2",Key {key1 = NumericValue, key2 = NoValue})]
))

旁注:

但我不确定如何将它们作为应用程序&备选方案,以便想法能够实现。

,无需将它们作为Applicative and Alternative的实例,<|>运算符应用于Parser(在Data.Aeson.Types中定义(,而不是用户定义的数据类型。CCD_ 27已经是CCD_ 28的一个实例。

最新更新