使用Aeson解析嵌套的异构JSON数组

时间:2016-04-06 04:22:17

标签: json haskell 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例子都没有用。我注意到名为withArray的API文档中的一个函数与String匹配,但我对于(Array -> Parser a)的使用内容感到遗憾

我真正坚持的部分是解析以String"容器"开头的异构数组。然后有一个包含所有对象的数组。到目前为止,我一直在直接索引对象数组,但是类型系统开始成为一个真正的迷宫,我发现很难以一种不丑陋和黑客的方式来解决这个问题。除此之外,Aeson不会产生非常有用的错误消息。

有关如何处理此事的任何想法?

1 个答案:

答案 0 :(得分:1)

在更复杂的示例中,最好记住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

我们必须对包列表进行newtype,因为FromJSON已经存在FromJSON a => FromJSON [a]个实例,并且它没有按照我们想要的方式执行(具体来说,它仅用于处理同类列表) 。

一旦我们这样做,我们就可以得到对象内部的hashmap,并将其键和值作为元组遍历。通过元组映射,我们生成[Parser ExpectedPacket],我们可以sequence生成Parser [ExpectedPacket]。我在这里使用lens来做无聊的事情,比如在打包和解包的字符串之间进行转换,或者将hashmap分解为键值元组。如果您不想提取text,则可以使用unordered-containerslens包来实现相同的目标。

它似乎适用于提供的示例:

λ> 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实例非常有帮助。