Haskell Aeson с типами сумм

У меня есть тип данных суммы, который выглядит так:

data Declaration =
    IndDecl { what :: String, name :: String, argnames :: Maybe [String], constructors :: [Constructor] }
    | TypeDecl { what :: String, name :: String, argnames :: Maybe [String], value :: Maybe Arg }
    | FixDecl { what :: String, fixlist :: Maybe [Fixitem] }
    | TermDecl { what :: String, name :: String, typ :: Maybe Typ, value :: Maybe Arg }
    deriving (Show, Eq)

И я получаю сериализатор / десериализатор JSON, используя Data.Aeson.TH со следующими параметрами:

$(deriveJSON defaultOptions { sumEncoding = UntaggedValue } ''Declaration)

Это удаляет тег из конструкции JSON. В то же время я думаю, что у моих данных уже есть тег, которым является поле what. Это может быть одно из следующих значений:

IndDecl : "decl:ind" 
FixDecl : "decl:fix"

и т.д. Поэтому тот факт, что я использую конфигурацию без тегов для своих десериализованных объектов JSON, вызывает беспокойство. Как я могу заставить Aeson получать заглушки JSON на основе тегов, которые уже есть в моих данных?

Или, может быть, лучше удалить поле what и позволить Aeson добавить поле «тег», которое описывает конструктор?

РЕДАКТИРОВАТЬ: Я попробовал последнее, добавив поле тега. Теперь это выглядит так:

-- Declarations
data Declaration =
    IndDecl { what :: String, tag :: String, name :: String, argnames :: Maybe [String], constructors :: [Constructor] }
    | TypeDecl { what :: String, tag :: String, name :: String, argnames :: Maybe [String], value :: Maybe Arg }
    | FixDecl { what :: String, tag :: String, fixlist :: Maybe [Fixitem] }
    | TermDecl { what :: String, tag :: String, name :: String, typ :: Maybe Typ, value :: Maybe Arg }
    deriving (Show, Eq)

Однако я получаю следующую ошибку:

Ошибка в $ .declarations [0]: ключевой "тег" отсутствует

Чего я не понимаю, так как создал поле «тег».


person rausted    schedule 21.04.2018    source источник


Ответы (1)


Если у вас есть контроль над структурой JSON, то есть вы не пытаетесь сопоставить существующий JSON, я бы использовал общие экземпляры ToJSON и FromJSON. Однако это означает, что у вас не может быть селектора, означающего tag. Экземпляры generic и Template Haskell резервируют ключевое слово tag для конструкторов типов суммы, потому что разные конструкторы могут иметь селекторы с одинаковыми именами (это случай вашего примера, или они могут иметь безымянные селекторы, которые содержат похожие типы, такие как data Wrapper = A Int | B Int.

{-# LANGUAGE DeriveGeneric #-}

import Data.Aeson
import GHC.Generics

-- I added these so it would compile
type Arg = String
type Fixitem = String
type Typ = String
type Constructor = String

data Declaration 
  = IndDecl 
    { what :: String
    , name :: String
    , argnames :: Maybe [String]
    , constructors :: [Constructor] 
    }
  | TypeDecl 
    { what :: String
    , name :: String
    , argnames :: Maybe [String]
    , value :: Maybe Arg 
    }
  | FixDecl 
    { what :: String
    , fixlist :: Maybe [Fixitem]
    }
  | TermDecl 
    { what :: String
    , name :: String
    , typ :: Maybe Typ
    , value :: Maybe Arg 
    }
  deriving (Show, Eq, Generic)

instance ToJSON Declaration
instance FromJSON Declaration

Теперь мы можем преобразовать значение Haskell в JSON:

λ> encode $ IndDecl "a" "b" (Just ["c", "d"]) ["e"]
"{\"tag\":\"IndDecl\",\"what\":\"a\",\"argnames\":[\"c\",\"d\"],\"constructors\":[\"e\"],\"name\":\"b\"}"

И мы также можем преобразовать строку JSON обратно в значение типа Declaration.

λ> decode $ "{\"tag\":\"FixDecl\",\"what\":\"a\"}" :: (Maybe Declaration)
Just (FixDecl {what = "a", fixlist = Nothing})

λ> decode $ "{\"tag\":\"TermDecl\",\"what\":\"a\",\"name\":\"c\",\"typ\":\"e\",\"value\":\"b\"}" :: (Maybe Declaration)
Just (TermDecl {what = "a", name = "c", typ = Just "e", value = Just "b"})
person MCH    schedule 12.05.2018