如何使用可以是两种不同类型之一的 Aeson 解析 JSON 字符串
How to parse a JSON string using Aeson that can be one of two different types
我目前正在努力使用 aeson
库解析一些 JSON 数据。当 属性 的数据不存在时,有许多属性的值为 false
。因此,如果 属性 的值通常是一个整数数组,而 属性 恰好没有数据,而不是提供一个空数组或 null
,则该值是 false
。 (这个数据的结构方式不是我做的,所以我必须以某种方式使用它。)
理想情况下,如果值为布尔值,我希望得到一个空列表。我在下面创建了一个小测试用例用于演示。因为我的 Group
数据构造函数需要一个列表,所以在遇到 false
.
时解析失败
data Group = Group [Int] deriving (Eq, Show)
jsonData1 :: ByteString
jsonData1 = [r|
{
"group" : [1, 2, 4]
}
|]
jsonData2 :: ByteString
jsonData2 = [r|
{
"group" : false
}
|]
instance FromJSON Group where
parseJSON = withObject "group" $ \g -> do
items <- g .:? "group" .!= []
return $ Group items
test1 :: Either String Group
test1 = eitherDecode jsonData1
-- returns "Right (Group [1,2,4])"
test2 :: Either String Group
test2 = eitherDecode jsonData2
-- returns "Left \"Error in $.group: expected [a], encountered Boolean\""
我最初希望 (.!=)
运算符允许它默认为一个空列表,但只有当 属性 完全不存在或 null
时才有效。如果是 "group": null
,它会解析成功,我会得到 Right (Group [])
.
关于如何让它成功解析的任何建议和 return 在这些情况下 false
的空列表?
解决此问题的一种方法是对对您的数据集有效的 JSON 数据构造函数 进行模式匹配,并引发 invalid 对于所有其他人。
例如,您可以为该特定字段编写类似这样的内容,请记住 parseJSON
是 Value -> Parser a
:
的函数
instance FromJSON Group where
parseJSON (Bool False) = Group <$> pure []
parseJSON (Array arr) = pure (Group $ parseListOfInt arr)
parseJSON invalid = typeMismatch "Group" invalid
parseListOfInt :: Vector Value -> [Int]
parseListOfInt = undefined -- build this function
您可以在 Aeson docs 中看到这方面的示例,非常好(但您必须仔细阅读它们并通读几遍)。
然后我可能会定义一个 separate 记录来表示该密钥所在的顶级对象并依赖于泛型派生,但其他人可能有更好的建议:
data GroupObj = GroupObj { group :: Group } deriving (Eq, Show)
instance FromJSON GroupObj
使用 Aeson 时要始终牢记的一件事是 core constructors(其中只有 6 个)和底层数据结构(HashMap
用于 Object
和 Vector
代表 Array
,例如)。
例如,在上面,当你在 Array arr
上进行模式匹配时,你必须意识到你在 arr
中得到了一个 Vector Value
并且我们仍然有需要做一些工作才能将其转换为整数列表,这就是为什么我在上面未定义另一个函数 parseListOfInt
的原因,因为我认为构建它可能是一个很好的练习?
我目前正在努力使用 aeson
库解析一些 JSON 数据。当 属性 的数据不存在时,有许多属性的值为 false
。因此,如果 属性 的值通常是一个整数数组,而 属性 恰好没有数据,而不是提供一个空数组或 null
,则该值是 false
。 (这个数据的结构方式不是我做的,所以我必须以某种方式使用它。)
理想情况下,如果值为布尔值,我希望得到一个空列表。我在下面创建了一个小测试用例用于演示。因为我的 Group
数据构造函数需要一个列表,所以在遇到 false
.
data Group = Group [Int] deriving (Eq, Show)
jsonData1 :: ByteString
jsonData1 = [r|
{
"group" : [1, 2, 4]
}
|]
jsonData2 :: ByteString
jsonData2 = [r|
{
"group" : false
}
|]
instance FromJSON Group where
parseJSON = withObject "group" $ \g -> do
items <- g .:? "group" .!= []
return $ Group items
test1 :: Either String Group
test1 = eitherDecode jsonData1
-- returns "Right (Group [1,2,4])"
test2 :: Either String Group
test2 = eitherDecode jsonData2
-- returns "Left \"Error in $.group: expected [a], encountered Boolean\""
我最初希望 (.!=)
运算符允许它默认为一个空列表,但只有当 属性 完全不存在或 null
时才有效。如果是 "group": null
,它会解析成功,我会得到 Right (Group [])
.
关于如何让它成功解析的任何建议和 return 在这些情况下 false
的空列表?
解决此问题的一种方法是对对您的数据集有效的 JSON 数据构造函数 进行模式匹配,并引发 invalid 对于所有其他人。
例如,您可以为该特定字段编写类似这样的内容,请记住 parseJSON
是 Value -> Parser a
:
instance FromJSON Group where
parseJSON (Bool False) = Group <$> pure []
parseJSON (Array arr) = pure (Group $ parseListOfInt arr)
parseJSON invalid = typeMismatch "Group" invalid
parseListOfInt :: Vector Value -> [Int]
parseListOfInt = undefined -- build this function
您可以在 Aeson docs 中看到这方面的示例,非常好(但您必须仔细阅读它们并通读几遍)。
然后我可能会定义一个 separate 记录来表示该密钥所在的顶级对象并依赖于泛型派生,但其他人可能有更好的建议:
data GroupObj = GroupObj { group :: Group } deriving (Eq, Show)
instance FromJSON GroupObj
使用 Aeson 时要始终牢记的一件事是 core constructors(其中只有 6 个)和底层数据结构(HashMap
用于 Object
和 Vector
代表 Array
,例如)。
例如,在上面,当你在 Array arr
上进行模式匹配时,你必须意识到你在 arr
中得到了一个 Vector Value
并且我们仍然有需要做一些工作才能将其转换为整数列表,这就是为什么我在上面未定义另一个函数 parseListOfInt
的原因,因为我认为构建它可能是一个很好的练习?