使用 Aeson 解析有问题的 JSON

Parsing problematic JSON with Aeson

我正在尝试解析 JSON 个对象,它们的形式通常是

{
  "objects": [a bunch of records that can assume a few different forms],
  "parameters": [same deal],
  "values": {
              "k1": "v1",
              "k2": "v2",
              ...
            }
}

使用 Haskell 的 Aeson 库。这个任务的一部分很简单,因为 parametersvalues 字段不需要任何自定义解析(因此似乎只需要 FromJSON 的一般派生实例),并且 [=与 objects 关联的数组中包含的 57=] 大多数 记录也不需要特殊解析。但是,解析 objects 数组中的记录的某些部分,当单独考虑时,有记录的解决方案,但一起提出了我还没有想出如何解决的问题。

现在,objectsparameters 数组中 record 的可能变体在数量上是有限的,并且通常包含相同的键;例如,它们都有一个 "name" 键或 "id" 键,等等。但它们中的许多都有一个 "type" 键,这是一个保留关键字,因此无法进行一般解析。这是第一个问题。

第二个问题是 objects 中记录的一种可能变体可能有一个键——比方说 "depends"——其值可能采用不同的类型。它可以是单个记录

{
  "objects": [
    {
      "depends": {
        "reference": "r1"
      },
    ...
  ],
  ...
}

或记录列表

{
  "objects": [
    "depends": [
      {"reference": "r1"},
      {"reference": "r2"},
      etc.
    ],
  ],
...
}

碰巧这是我想在转换为 Haskell 对象后以自定义方式操作的一个字段(最终我想表示此类 "depends" 引用的集合作为 Data.Graph 图)。

我最初的尝试是创建一个巨大的记录类型,它包含 objectsparameters 数组元素中所有可能的键。像这样:

{-# LANGUAGE DeriveAnyClass    #-}
{-# LANGUAGE DeriveGeneric     #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards   #-}

import Data.Aeson
import GHC.Generics

data Ref = Ref
  { ref :: String
  } deriving (Show, Generic, FromJSON, ToJSON)

data Reference 
  = Reference Ref
  | References [Ref]
  deriving (Show, Generic, FromJSON, ToJSON)

type MString = Maybe String -- I'm writing this a lot using this approach

data PObject = PObject 
  -- Each of the object/parameter records have these keys
  { _name    :: String
  , _id      :: String

  -- Other keys that might appear in a given object/parameter record
  , _type    :: MString
  , _role    :: MString
  , _depends :: Maybe Reference

  -- A bunch more
  } deriving Show

instance FromJSON PObject where
  parseJSON = withObject "PObject" $ \o -> do
    _name    <- o .: "name"
    _id      <- o .: "id"
    _type    <- o .:? "type"
    _role    <- o .:? "role"
    _depends <- o .:? "depends"
    -- etc.
    return PObject{..}

最后,整个 JSON 对象将被表示为

data MyJSONObject = MyJSONObject
  { objects    :: Maybe [PObject]
  , parameters :: Maybe [PObject]
  , values     :: Maybe Object
  } deriving (Show, Generic, FromJSON)

这一直有效,直到它尝试解析 "depends" 字段,并报告

"Error in $.objects[2].depends: key \"tag\" not present"

没有 "tag" 键,所以我不确定这是什么意思。我怀疑它与 RefReferenceFromJSON 的通用实例有关。

我的问题:

  1. 这个错误说明了什么?到目前为止,在我学习 Haskell 的过程中,错误总是很有帮助。这个不是。我是否需要为我的 parseJSON 函数中的 "depends" 键做一些特殊的事情?
  2. 所有这些样板实际上都是因为两个键——"type" 和 "depends"。有没有更优雅的方式来处理这些键?
  3. 相关地,这是我第一个 real Haskell 项目的一部分,所以我有一个更一般的设计问题。有经验的 Haskeller 和 Aeson 用户,您如何为这种类型的 JSON 布置您的类型和实例?我尝试列出 objects/parameters 记录的每个可能变体作为其自己的单独类型,并且只为那些具有 "depends" 或 [=73] 的实例编写自定义 FromJSON 实例=] 键,但这会产生更多的样板代码,并且无论如何都不能解决我遇到的任何其他问题。关于 "best practices"、惯用用法等的一般指示将非常有用并受到赞赏。

There are no "tag" keys, so I'm not sure what this means. I suspect it has to do with the generic instances of FromJSON for Ref and Reference.

说得对。通过 default, aeson will use the defaultTaggedObject 来编码总和类型。 References 是求和类型。所以aeson引入了一个tag来区分构造函数。你可以用一个简短的例子来尝试:

ghci> data Example = A () | B deriving (Generic,ToJSON)
ghci> encode B
"{\"tag\":\"B\",\"contents\":[]}"

当您使用 _depends <- o .:? "depends" 时,Reference 解析器找不到它的标签。你必须自己在那里写一些解析代码。

All of this boilerplate is really because of two keys -- "type" and "depends". Is there a more elegant way to deal with these keys?

您可以保留字段名称中的下划线,并使用 fieldLabelModifier in the Options 数据类型将其剥离以进行解析。