Search code examples
jsonhaskellaeson

aeson ToJSON instances for polymorphic type


How to write a aeson ToJSON instances by hand for a polymorphic type like:

data Show a => Translatable a = Translatable (Map.Map String a)
    deriving (Show, Eq, Typeable)

I would like to encode a

Translatable $ Map.fromList [("key", "value"), ("key2", "value2")]

to a json object like { "key", "value", "key2", "value2" }

What I tried so far is this:

import qualified Data.Aeson as A
import Data.Data (Typeable)
import qualified Data.Map as Map

data Show a => Translatable a = Translatable (Map.Map String a)
    deriving (Show, Eq, Typeable)

instance Show a => A.ToJSON (Translatable a) where
    toEncoding xs = A.object $ map (.=) (Map.toList xs)

Error

 Couldn't match type ‘A.Value’
 with ‘Data.Aeson.Encoding.Internal.Encoding' A.Value’
      Expected type: A.Encoding
        Actual type: A.Value

The aeson documentation showes: type Encoding = Encoding' Value

But A.Encoding' is not in scope

Not in scope: data constructor ‘A.Encoding'’

what am I doing wrong?

EDIT:

I changed my Translatable to a type

type Translatable a = Map.Map String a

And now it works without adding Translatable as instance to ToJSON

But the initial question is still the question.


Solution

  • You are mixing the toJSON and toEncoding functions up. Just implement the toJSON function of your ToJSON instance. I imported Data.Aeson unqualified, to have less of a struggle with the operators.

    import Data.Aeson
    import qualified Data.Text as T
    
    --- etc.
    
    instance Show a => ToJSON (Translatable a) where
        toJSON (Translatable myMap) = toJSON
            [ object [ T.pack key .= show val ] | (key, val) <- Map.toList myMap ]
    

    We use the toJSON function on a list of json objects to create a json array. T.pack key is needed, since your map keys are Strings, but .= needs a Text.