Sean Clark Hess
Sean Clark Hess

Reputation: 16059

Haskell JSON Rest API — how to generically serialize meta information?

I'm creating a JSON REST API (lots of caps there), and already have Data.Aeson.Generic working nicely. In the following, serializedString would be {"x":10, "y":10}

import qualified Data.Aeson.Generic as A
import Data.Data (Data, Typeable)
data Unit = Unit { x :: Int, y :: Int } deriving (Show, Eq, Data, Typeable)

example = do
    let serializedByteString = A.encode (Unit 10 10)

I would like to have my api respond like this for successes:

{unit:{x:10, y:10}} 

And this for failures

{error:"Didn't work!"}

I was thinking of making some kind of Response data type, with Response and Error constructors. It's easy to serialize Error, but response could have all kinds of different objects, and rather than send back {data:{...}} I'd like to do {unit:{...}}.

Is there a way to define my Response value constructor so that it works with anything deriving Data? Is there a way to know what the name of the value constructor is when I go to serialize my object? show knows it somehow. Thanks!

Upvotes: 2

Views: 622

Answers (1)

Sean Clark Hess
Sean Clark Hess

Reputation: 16059

Doesn't work for the Error constructor yet (I think), but this works for unit

{-# LANGUAGE OverloadedStrings, DeriveDataTypeable #-}

module Types where

import Data.Data (Data, Typeable, typeOf)
import Data.Aeson (ToJSON(..), (.=), object)
import qualified Data.Aeson.Generic as AG

import qualified Data.Text as T
import Data.Char (toLower)

data Unit = Unit { x :: Int, y :: Int } deriving (Show, Data, Typeable)


data Message a = Message { obj :: a } |
                 Error String 
                 deriving (Show, Data, Typeable)


instance (Data a, Typeable a) => ToJSON (Message a) where
    toJSON m = object [T.pack typeName .= AG.toJSON (obj m)]
        where o = obj m
              typeName = map toLower $ show $ typeOf o

You have to use Data.Aeson.encode, rather than Data.Aeson.Generic.encode on the top-level message. The key concept I was missing was typeOf from Data.Data.Typeable. It gives you a string representing the data constructor. It will be interesting to try to go the other direction using FromJSON

Edit: to be clear, you can then call Data.Aeson.encode $ Message $ Unit 10 10 and get {unit:{x:10, y:10}}

Upvotes: 1

Related Questions