Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tidy serialisation of ADTs to JSON. #267

Closed
chrisdew opened this issue Jul 15, 2015 · 2 comments
Closed

Tidy serialisation of ADTs to JSON. #267

chrisdew opened this issue Jul 15, 2015 · 2 comments

Comments

@chrisdew
Copy link

Crossposted: http://stackoverflow.com/questions/31439991/how-to-write-haskell-adts-as-tidy-json-using-aeson

(What is tidy is in the eye of the beholder, but I believe: {"attach":{"tel":"+447890"}} is tidier than {"tag":"AttachMsg","contents":{"tel":"+447890"}}.)

I've spent some time playing around with Aeson, but I can't get Algebraic Data Types to serialise nicely.

What I've tried is:

data Attach = Attach { tel :: String }
              deriving (Show)
$(deriveJSON defaultOptions ''Attach)

data Fix = Fix { lat :: Double, lng :: Double }
              deriving (Show)
$(deriveJSON defaultOptions ''Fix)

data MsgIn = AttachMsg Attach
           | FixMsg    Fix
           deriving (Show)
$(deriveJSON defaultOptions ''MsgIn)

data MsgIn2 = MsgIn2 { attach :: Maybe Attach, fix :: Maybe Fix }
            deriving (Show)
$(deriveJSON defaultOptions ''MsgIn2)

someFunc :: IO ()
someFunc = do
  let attach = Attach "+447890"
  let reply = AttachMsg attach
  BL.putStrLn (encode reply)
  let reply2 = MsgIn2 (Just attach) Nothing
  BL.putStrLn (encode reply2)

The output is:

{"tag":"AttachMsg","contents":{"tel":"+447890"}}
{"attach":{"tel":"+447890"},"fix":null}

The output I'm looking for is:

{"attach":{"tel":"+447890"}}

but from the MsgIn type, rather than MsgIn2.

(The output of MsgIn2 gets quite close, but it's got an explicit null.)

Is there a way of doing this in Aeson?

@chrisdew
Copy link
Author

Useful answers on SO.

@Gekkio
Copy link

Gekkio commented Jul 15, 2015

Use custom options instead of defaultOptions. You can get the right structure by using sumEncoding = ObjectWithSingleField, which reduces your first example to {"AttachMsg":{"tel":"+447890"}}. You can then customize the constructor tags by using constructorTagModifier = myConstructorTag and by writing a function myConstructorTag that customizes the names to your liking (e.g. AttachMsg -> attach).

As an example, you'll get the output you want by writing this into a separate module, importing it, and using myOptions instead of defaultOptions:

myConstructorTag :: String -> String
myConstructorTag "AttachMsg" = "attach"
myConstructorTag x = x

myOptions :: Options
myOptions = defaultOptions {sumEncoding = ObjectWithSingleField, constructorTagModifier = myConstructorTag}

A separate module is needed here because of Template Haskell. There's probably a way to define myConstructorTag and myOptions in a better way to satisfy the needs of TH, but I have absolutely no idea how to do that.

Edit: I cross-posted my answer to SO as well

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants