Search code examples
haskellaeson

Single tag constructors in Aeson


I have a data type like this:

data A = A T.Text deriving (Generic, Show)

instance A.ToJSON A 

If I use A.encode to it:

A.encode $ A "foobar" -- "foobar"

Then I use singleTagConstructors on it:

instance A.ToJSON A where
  toEncoding a = A.genericToEncoding $ A.defaultOptions { A.tagSingleConstructors = True }

A.encode $ A "foobarquux" -- "{tag: A, contents: foobarquux}"

At some point I made another data type:

newtype Wrapper a = Wrapper 
  { unWrap :: a
  } deriving (Show)

instance A.ToJSON a => A.ToJSON (Wrapper a) where 
  toJSON w = A.object [ "wrapped" A..= unWrap w ]

Here's the part where I get confused:

A.encode $ Wrapper $ A "foobar" -- "{wrapped: foobar}"

How do I get the result to be like this?

"{wrapped: {tag: A, contents: foobarquux}}"

Solution

  • To answer the question directly, you can always implement the Wrapper instance with tagSingleConstructors = False, like this:

    instance Generic a => A.ToJSON (Wrapper a) where 
      toJSON w = A.object [ "wrapped" A..= encA (unWrap w) ]
        where
          encA = A.genericToEncoding $ A.defaultOptions { A.tagSingleConstructors = False }
    

    But I don't see why you'd want to do that.

    If you control the API, then you don't need the tag field: the expected type of the wrapped value is already statically known, so tag would not be helpful.

    And if you don't control the API, I would recommend representing it very explicitly, for example as a record that exactly matches the API shape. Otherwise you run a risk of accidentally breaking the API by making unrelated changes in remote parts of the codebase.