Разбор проблемного JSON с Aeson

Я пытаюсь проанализировать объекты JSON, которые обычно имеют вид

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

используя библиотеку Aeson Хаскелла. Часть этой задачи проста в том смысле, что parameters а также values поля не нуждаются ни в каком пользовательском анализе (и поэтому, похоже, требуется только обобщенный экземпляр FromJSON) и большинство записей, содержащихся в массиве, связанном с objects Также не нужно специального разбора. Тем не менее, есть некоторые части анализа записей в массиве objects что, если рассматривать отдельно, есть документированные решения, но вместе представляют проблемы, которые я не выяснил, как решить.

Теперь возможные варианты записи внутри objects а также parameters массивы конечны по числу и часто содержат одинаковые ключи; например, все они имеют ключ "name" или ключ "id", или тому подобное. Но также у многих из них есть ключ типа, который является зарезервированным ключевым словом, и поэтому не может быть проанализирован в общем. Это первая проблема.

Вторая проблема заключается в том, что один из возможных вариантов записи внутри objects может иметь ключ - скажем, "зависит" - значение которого может принимать разные типы. Это может быть одна запись

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

или список записей

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

и бывает, что это единственное поле, которым я хотел бы манипулировать нестандартным образом после преобразования в объект Haskell (в конце концов я хочу представить коллекцию таких "зависимых" ссылок в виде Data.Graph график).

Моя первоначальная попытка состояла в том, чтобы создать один огромный тип записи, который включает все возможные ключи в элементах objects а также parameters массивы. Что-то вроде этого:

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

Здесь нет клавиш тегов, поэтому я не уверен, что это значит. Я подозреваю, что это связано с общими случаями FromJSON за Ref а также Reference,

Мои вопросы:

  1. Что означает эта ошибка? До сих пор в моем изучении Haskell ошибки всегда были очень полезны. Это не так. Нужно ли делать что-то особенное для ключа "зависит" в моем parseJSON функционировать?
  2. Весь этот шаблон действительно из-за двух ключей - "тип" и "зависит". Есть ли более элегантный способ справиться с этими ключами?
  3. Кроме того, это часть моего первого настоящего проекта на Haskell, поэтому у меня есть более общий вопрос о дизайне. Опытные пользователи Haskellers и Aeson, как бы вы изложили свои типы и экземпляры для этого типа JSON? Я попытался перечислить все возможные варианты objects/parameters запись как отдельный отдельный тип, и только написание пользовательских FromJSON экземпляры для тех, у кого есть ключ "зависит" или "тип", но это произвело намного больше стандартного кода и в любом случае не решает ни одну из других проблем, которые у меня есть. Общие указания на "лучшие практики", идиоматическое использование и т. Д. Были бы чрезвычайно полезны и оценены.

2 ответа

Решение

Здесь нет клавиш тегов, поэтому я не уверен, что это значит. Я подозреваю, что это связано с общими случаями FromJSON за Ref а также Reference,

Это место. По умолчанию Aeson будет использовать defaultTaggedObject кодировать суммы типов. References является типом суммы. Поэтому Aeson вводит тег для различения конструкторов. Вы можете попробовать это на коротком примере:

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

Когда вы используете _depends <- o .:? "depends", Reference парсер не находит свой тег. Вы должны написать там немного кода для разбора.

Весь этот шаблон действительно из-за двух ключей - "тип" и "зависит". Есть ли более элегантный способ справиться с этими ключами?

Вы можете сохранить подчеркивания в именах полей и использовать fieldLabelModifier в Options Тип данных, чтобы лишить их для анализа.

Другие вопросы по тегам