1
votes

I have a sum data type that looks like this:

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)

And I'm deriving the JSON serializer/deserializer using Data.Aeson.TH with the following options:

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

That removes the tag from the JSON construction. At the same time, I think my data already has a tag, which is the what field. It can be one of the following:

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

etc. So the fact that I'm using an Untagged configuration for my JSON deserialized objects is kind of worrisome. How can I make Aeson derive JSON stubs based on the tags that my data already has?

Or maybe it's a better idea to remove the what field and let Aeson add a "tag" field which describes the constructor?

EDIT: I tried the latter, by adding a tag field. Now it looks like:

-- 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)

However, I get the following error:

Error in $.declarations[0]: key "tag" not present

Which I don't understand, since I created a "tag" field.

1
You misunderstand how Aeson works. Try serializing your data and see what comes out.Fyodor Soikin
The Data.Aeson.TH documentation suggests you want sumEncoding = defaultTaggedObject { tagFieldName = "what" } to use your preexisting field as the tag.duplode
You don't need the what field in the data declaration. You already have the constructor for that. The different tags options in AESON are only relevant to the JSON itself, not the Haskell structures. So, just remove the what field.mb14
I agree on removing the "what" field from my structures, but what about the last error key "tag" not present even when I add a tag? (see EDIT)rausted

1 Answers

3
votes

If you have control over the structure of the JSON, meaning you are not trying to match an existing JSON, I would use the generic instances of ToJSON and FromJSON. However, this means you cannot have a selector that means tag. The generic and Template Haskell instances reserve the keyword tag for the constructors of sum types, because different constructors could have the similarly named selectors (this is the case for your example, or they could have unnamed selectors that hold similar types like 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

Now we can convert a Haskell value to JSON:

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

And we can also convert a JSON string back to value of the Declaration type.

λ> 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"})