aeson icon indicating copy to clipboard operation
aeson copied to clipboard

allNullaryToStringTag has different results if sum has records

Open Vonfry opened this issue 1 month ago • 1 comments

Here is the example codes:

{-# LANGUAGE DeriveGeneric #-}
module Main where

import Data.Aeson
import GHC.Generics

data Foo = Foo | Bar { bar :: Int } deriving (Generic, Show)

serdeOptions :: Options
serdeOptions = defaultOptions
    { sumEncoding = ObjectWithSingleField
    , allNullaryToStringTag = True
    }

instance ToJSON Foo where
    toJSON = genericToJSON serdeOptions

main = do
    print $ encode $ Foo
    print $ encode $ Bar { bar = 1 }

It prints:

"{\"Foo\":[]}"
"{\"Bar\":{\"bar\":1}}"

As my expect, the first should be "\"Foo\"", which is the default behavior of serde if an enum without arguments.

If Foo is defined as data Foo = Foo | Bar, the output is "\"Foo\"" and "\"Bar\"". If Foo is defined as data Foo = Foo {} | ... the output is "{\"Foo\":[]}" as well.

Is the current behavior a feature or bug? If it is a feature, is it a good idea to add a new option in sumEncoding to support this?

In my opinion,

  • data Foo = Foo | Bar { ... } should be "\"Foo\""
  • data Foo = Foo () | ... should be {\"Foo\":[]}
  • data Foo = Foo {} | ... should be {\"Foo\":{}}

Vonfry avatar Nov 30 '25 06:11 Vonfry

is it a good idea to add a new option in sumEncoding

No. I don't think it's a good idea. Adding any new options to generic deriving machinery will solve much.

Making it highly configurable was a mistake. It's a PITA to maintain and develop (and test).


FWIW

data Foo = Foo {}

is not considered to be defined using record syntax. There is no "nullary record", it's not a thing.

phadej avatar Dec 01 '25 14:12 phadej