【问题标题】:Parsing problematic JSON with Aeson使用 Aeson 解析有问题的 JSON
【发布时间】:2017-10-22 17:39:15
【问题描述】:

我正在尝试解析 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 关联的数组中包含的记录也不需要特殊解析。但是,在解析objects 数组中的记录的某些部分,单独考虑时,已经记录了解决方案,但一起提出了我还没有弄清楚如何解决的问题。

现在,objectsparameters 数组中可能的记录变体数量有限,并且通常包含相同的键;例如,它们都有一个“name”键或一个“id”键,等等。但它们中的许多也有一个“类型”键,这是一个保留关键字,因此不能通用解析。这是第一个问题。

第二个问题是objects 内的记录的一种可能变体可以有一个键——比如说“依赖”——它的值可能采用不同的类型。可以是单条记录

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

或记录列表

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

碰巧这是我想在转换为 Haskell 对象后以自定义方式操作的一个字段(最终我想将此类“依赖”引用的集合表示为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)

这一直有效,直到它尝试解析“依赖”字段并报告该字段

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

没有“标签”键,所以我不确定这是什么意思。我怀疑这与RefReferenceFromJSON 的通用实例有关。

我的问题:

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

【问题讨论】:

  • 试着把它归结为一个问题。您的第三个问题可以在您的代码运行后立即在Code Review 上回答。

标签: json haskell aeson


【解决方案1】:

没有“标签”键,所以我不确定这是什么意思。我怀疑这与FromJSON 的通用实例RefReference 有关。

那就对了。 default, aeson 将使用 defaultTaggedObject 来编码 sum 类型。 References 是求和类型。因此,aeson 引入了一个标签来区分构造函数。你可以用一个简短的例子来试试:

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

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

【讨论】:

    【解决方案2】:

    所有这些样板实际上都是因为两个键——“类型”和 “要看”。有没有更优雅的方式来处理这些键?

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多