使用 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 用于对象。比我们习惯处理的列表和映射更奇特一点,但仍然是具有 FoldableTraversable 实例的数据结构。考虑到这一点,我们可以声明这些实例:

{-# 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.

,你可以使用 textunordered-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"}]}])

最后,我经常发现使用 typeMismatcheitherDecode 对调试 Aeson 实例非常有帮助。