Haskell: 将一个可以是多种类型的对象解析为一种类型
Haskell: Parsing an object that could be multiple types into one single type
我是一个 haskell 初学者,正在经历 aeson,通过解析一些数据文件来了解更多关于两者的信息。
通常有一个数据文件,可能是.json
、lua
、table、.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)
但是,如果像第一个示例那样,键不仅可以没有值,而且还可以有完全不同的值。
如果在其中您只关心数字或字符串怎么办?有没有一种方法可以在不超出类型定义的情况下解析它们?
通过一些快速搜索,我发现替代方案 class 就是针对此类问题的,而像 *>
、<>
、<|>
这样的运算符可以证明有用,但我不确定如何。
我知道如果我只想要文本或数字,我需要定义一个可以封装所有三种机会的类型,比如
Data NeededVal = NoValue | TextValue | Needed Int
或
Data NeededVal = NoValue | NumericValue | Needed String
但我不确定如何让它们成为 Applicative & Alternative 的实例,以便这个想法能够实现。
这是我之前 question
的简短跟进
好吧,我尝试使用 JSON 如下所示:
"root": {
"m1": {
"key1": "value1",
"key2": 2
},
"m2": {
"key1": 1
},
}
并使用 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)
为了处理 NoValue
,我使用 Alternative <|>
as
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)
为了测试 String
和 numeric
类型,我使用 Value
构造函数作为:
instance FromJSON NeededVal where
parseJSON (String txt) = return $ Needed $ unpack txt
parseJSON (Number _) = return $ NumericValue
parseJSON _ = return NoValue
跳过 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'
最终结果:
Right (Root (fromList [
("m1",Key {key1 = Needed "value1", key2 = NumericValue}),
("m2",Key {key1 = NumericValue, key2 = NoValue})]
))
旁注:
but I'm not sure how I'd go about making them an instance of
Applicative & Alternative so that the idea would work out.
No,无需将它们作为 Applicative and Alternative
的实例,<|>
运算符应用于 Parser
(定义在 Data.Aeson.Types
) 不是用户定义的数据类型。 Parser
已经是 Alternative
的实例。
我是一个 haskell 初学者,正在经历 aeson,通过解析一些数据文件来了解更多关于两者的信息。
通常有一个数据文件,可能是.json
、lua
、table、.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)
但是,如果像第一个示例那样,键不仅可以没有值,而且还可以有完全不同的值。
如果在其中您只关心数字或字符串怎么办?有没有一种方法可以在不超出类型定义的情况下解析它们?
通过一些快速搜索,我发现替代方案 class 就是针对此类问题的,而像 *>
、<>
、<|>
这样的运算符可以证明有用,但我不确定如何。
我知道如果我只想要文本或数字,我需要定义一个可以封装所有三种机会的类型,比如
Data NeededVal = NoValue | TextValue | Needed Int
或
Data NeededVal = NoValue | NumericValue | Needed String
但我不确定如何让它们成为 Applicative & Alternative 的实例,以便这个想法能够实现。
这是我之前 question
的简短跟进好吧,我尝试使用 JSON 如下所示:
"root": {
"m1": {
"key1": "value1",
"key2": 2
},
"m2": {
"key1": 1
},
}
并使用 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)
为了处理 NoValue
,我使用 Alternative <|>
as
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)
为了测试 String
和 numeric
类型,我使用 Value
构造函数作为:
instance FromJSON NeededVal where
parseJSON (String txt) = return $ Needed $ unpack txt
parseJSON (Number _) = return $ NumericValue
parseJSON _ = return NoValue
跳过 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'
最终结果:
Right (Root (fromList [
("m1",Key {key1 = Needed "value1", key2 = NumericValue}),
("m2",Key {key1 = NumericValue, key2 = NoValue})]
))
旁注:
but I'm not sure how I'd go about making them an instance of Applicative & Alternative so that the idea would work out.
No,无需将它们作为 Applicative and Alternative
的实例,<|>
运算符应用于 Parser
(定义在 Data.Aeson.Types
) 不是用户定义的数据类型。 Parser
已经是 Alternative
的实例。