使用 Aeson 解析嵌套异构 JSON 数组
Parsing nested heterogeneous JSON arrays with Aeson
因此,我在使用 Haskell Aeson 库解析以下 JSON 时遇到了一些障碍。
假设我有以下内容:
"packetX_Name": [
"container",
[
{
"field1": "value1",
"field2": "value2"
},
{
"field1": "value3",
"field2": "value4"
},
{
"field1": "value5",
"field2": "value6"
}
]
],
"packetY_Name": [
"container",
[
{
"field1": "value7",
"field2": "value8"
},
{
"field1": "value9",
"field2": "value10"
}
]
],
etc...
理想情况下,我希望使用这样的数据类型来解析它:
data ExtractedPacket = ExtractedPacket
{ packetName :: String
, packetFields :: [ExtractedPacketField]
} deriving (Show,Eq)
instance FromJSON ExtractedPacket where
parseJSON = blah
data ExtractedPacketField = ExtractedPacketField
{ field1 :: String
, field2 :: String
} deriving (Show,Eq)
instance FromJSON ExtractedPacketField where
parseJSON = blah
并得到如下内容:
ExtractedPacket
"packetX_Name"
[ ExtractedPacketField "value1" "value2"
, ExtractedPacketField "value3" "value4"
, ExtractedPacketField "value5" "value6"
]
ExtractedPacket
"packetY_Name"
[ ExtractedPacketField "value7" "value8"
, ExtractedPacketField "value10" "value10"
]
此 JSON 示例描述的是网络数据包,每个数据包都有不同的名称(例如 "packetX_Name"),无法以相同的方式解析 "field1" 或 "field2" 可以。每次都会不一样。当涉及到这种情况时,大多数 Aeson 示例都毫无帮助。我注意到 API 文档中有一个名为 withArray
的函数与 String
相匹配,但我不知道 (Array -> Parser a)
[= 使用什么17=]
我真正坚持的部分是解析以字符串 "container" 开头的异构数组,然后是一个包含所有对象的数组。到目前为止,我一直在直接对对象数组进行索引,但是类型系统开始变成一个真正的迷宫,我发现很难以不丑陋和骇人听闻的方式处理它。最重要的是,Aeson 不会产生非常有用的错误消息。
关于如何处理这个问题有什么想法吗?
在像这样更复杂的示例中,最好记住 Aeson Value
类型下面是简单的数据结构 – Vector
用于数组,HashMap
用于对象。比我们习惯处理的列表和映射更奇特一点,但仍然是具有 Foldable
和 Traversable
实例的数据结构。考虑到这一点,我们可以声明这些实例:
{-# LANGUAGE OverloadedStrings #-}
import qualified Control.Lens as Lens
import qualified Data.Foldable as Foldable
import qualified Data.Text.Strict.Lens as Lens
import Data.Aeson
import Data.Aeson.Types
newtype ExtractedPackets =
ExtractedPackets [ExtractedPacket] deriving (Show)
instance FromJSON ExtractedPackets where
parseJSON (Object o) = do
let subparsers =
[ ExtractedPacket (Lens.view Lens.unpacked key) <$> parseJSON packets
| (key, Array values) <- Lens.itoList o
, packets@(Array _) <- Foldable.toList values]
packets <- sequence subparsers
return (ExtractedPackets packets)
parseJSON invalid =
typeMismatch "ExtractedPackets" invalid
instance FromJSON ExtractedPacketField where
parseJSON (Object o) =
ExtractedPacketField <$> o .: "field1" <*> o .: "field2"
parseJSON invalid =
typeMismatch "ExtractedPacketField" invalid
我们必须重新输入数据包列表,因为 FromJSON a => FromJSON [a]
已经有一个 FromJSON
实例,但它不能满足我们的要求(具体来说,它只能处理同类列表)。
一旦我们这样做了,我们就可以得到对象内部的 hashmap,并以元组的形式遍历它的键和值。映射到元组上,我们生成一个 [Parser ExpectedPacket]
,我们可以将其 sequence
转换为 Parser [ExpectedPacket]
。我在这里自由地使用 lens
来做一些无聊的事情,比如在打包字符串和未打包字符串之间进行转换,或者将散列图分解为键值对元组。如果你不想引入 lens
.
,你可以使用 text
和 unordered-containers
包来实现相同的目标
它似乎适用于提供的示例:
λ> eitherDecode bytes :: Either String ExtractedPackets
Right (ExtractedPackets [ExtractedPacket {packetName = "packetX_Name",
packetFields = [ExtractedPacketField {field1 = "value1", field2 =
"value2"},ExtractedPacketField {field1 = "value3", field2 =
"value4"},ExtractedPacketField {field1 = "value5", field2 =
"value6"}]},ExtractedPacket {packetName = "packetY_Name", packetFields
= [ExtractedPacketField {field1 = "value7", field2 =
"value8"},ExtractedPacketField {field1 = "value9", field2 =
"value10"}]}])
最后,我经常发现使用 typeMismatch
和 eitherDecode
对调试 Aeson 实例非常有帮助。
因此,我在使用 Haskell Aeson 库解析以下 JSON 时遇到了一些障碍。
假设我有以下内容:
"packetX_Name": [
"container",
[
{
"field1": "value1",
"field2": "value2"
},
{
"field1": "value3",
"field2": "value4"
},
{
"field1": "value5",
"field2": "value6"
}
]
],
"packetY_Name": [
"container",
[
{
"field1": "value7",
"field2": "value8"
},
{
"field1": "value9",
"field2": "value10"
}
]
],
etc...
理想情况下,我希望使用这样的数据类型来解析它:
data ExtractedPacket = ExtractedPacket
{ packetName :: String
, packetFields :: [ExtractedPacketField]
} deriving (Show,Eq)
instance FromJSON ExtractedPacket where
parseJSON = blah
data ExtractedPacketField = ExtractedPacketField
{ field1 :: String
, field2 :: String
} deriving (Show,Eq)
instance FromJSON ExtractedPacketField where
parseJSON = blah
并得到如下内容:
ExtractedPacket
"packetX_Name"
[ ExtractedPacketField "value1" "value2"
, ExtractedPacketField "value3" "value4"
, ExtractedPacketField "value5" "value6"
]
ExtractedPacket
"packetY_Name"
[ ExtractedPacketField "value7" "value8"
, ExtractedPacketField "value10" "value10"
]
此 JSON 示例描述的是网络数据包,每个数据包都有不同的名称(例如 "packetX_Name"),无法以相同的方式解析 "field1" 或 "field2" 可以。每次都会不一样。当涉及到这种情况时,大多数 Aeson 示例都毫无帮助。我注意到 API 文档中有一个名为 withArray
的函数与 String
相匹配,但我不知道 (Array -> Parser a)
[= 使用什么17=]
我真正坚持的部分是解析以字符串 "container" 开头的异构数组,然后是一个包含所有对象的数组。到目前为止,我一直在直接对对象数组进行索引,但是类型系统开始变成一个真正的迷宫,我发现很难以不丑陋和骇人听闻的方式处理它。最重要的是,Aeson 不会产生非常有用的错误消息。
关于如何处理这个问题有什么想法吗?
在像这样更复杂的示例中,最好记住 Aeson Value
类型下面是简单的数据结构 – Vector
用于数组,HashMap
用于对象。比我们习惯处理的列表和映射更奇特一点,但仍然是具有 Foldable
和 Traversable
实例的数据结构。考虑到这一点,我们可以声明这些实例:
{-# LANGUAGE OverloadedStrings #-}
import qualified Control.Lens as Lens
import qualified Data.Foldable as Foldable
import qualified Data.Text.Strict.Lens as Lens
import Data.Aeson
import Data.Aeson.Types
newtype ExtractedPackets =
ExtractedPackets [ExtractedPacket] deriving (Show)
instance FromJSON ExtractedPackets where
parseJSON (Object o) = do
let subparsers =
[ ExtractedPacket (Lens.view Lens.unpacked key) <$> parseJSON packets
| (key, Array values) <- Lens.itoList o
, packets@(Array _) <- Foldable.toList values]
packets <- sequence subparsers
return (ExtractedPackets packets)
parseJSON invalid =
typeMismatch "ExtractedPackets" invalid
instance FromJSON ExtractedPacketField where
parseJSON (Object o) =
ExtractedPacketField <$> o .: "field1" <*> o .: "field2"
parseJSON invalid =
typeMismatch "ExtractedPacketField" invalid
我们必须重新输入数据包列表,因为 FromJSON a => FromJSON [a]
已经有一个 FromJSON
实例,但它不能满足我们的要求(具体来说,它只能处理同类列表)。
一旦我们这样做了,我们就可以得到对象内部的 hashmap,并以元组的形式遍历它的键和值。映射到元组上,我们生成一个 [Parser ExpectedPacket]
,我们可以将其 sequence
转换为 Parser [ExpectedPacket]
。我在这里自由地使用 lens
来做一些无聊的事情,比如在打包字符串和未打包字符串之间进行转换,或者将散列图分解为键值对元组。如果你不想引入 lens
.
text
和 unordered-containers
包来实现相同的目标
它似乎适用于提供的示例:
λ> eitherDecode bytes :: Either String ExtractedPackets
Right (ExtractedPackets [ExtractedPacket {packetName = "packetX_Name",
packetFields = [ExtractedPacketField {field1 = "value1", field2 =
"value2"},ExtractedPacketField {field1 = "value3", field2 =
"value4"},ExtractedPacketField {field1 = "value5", field2 =
"value6"}]},ExtractedPacket {packetName = "packetY_Name", packetFields
= [ExtractedPacketField {field1 = "value7", field2 =
"value8"},ExtractedPacketField {field1 = "value9", field2 =
"value10"}]}])
最后,我经常发现使用 typeMismatch
和 eitherDecode
对调试 Aeson 实例非常有帮助。