Разбор проблемного 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
,
Мои вопросы:
- Что означает эта ошибка? До сих пор в моем изучении Haskell ошибки всегда были очень полезны. Это не так. Нужно ли делать что-то особенное для ключа "зависит" в моем
parseJSON
функционировать? - Весь этот шаблон действительно из-за двух ключей - "тип" и "зависит". Есть ли более элегантный способ справиться с этими ключами?
- Кроме того, это часть моего первого настоящего проекта на 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
Тип данных, чтобы лишить их для анализа.