Search code examples
jsonhaskellaeson

Aeson encoding of Data.Map.Strict.Map with custom key type results in array of arrays instead of object


I am having trouble getting Aeson to spit out objects when I use custom types as keys. Let me demonstrate:

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveAnyClass #-}

import Data.Aeson

import qualified Data.Map.Strict as M
import qualified Data.ByteString.Lazy.Char8 as B

import GHC.Generics

data LOL = A | B | C deriving (Eq, Ord, Generic, ToJSONKey, ToJSON)
main = do
    B.putStrLn $ encode $ M.fromList [(A,"b")]
    B.putStrLn $ encode $ M.fromList [("A","b")]

In one case, I get an array of arrays, while in the other it's a regular object:

$ ./tojsonkey 
[["A","b"]]
{"A":"b"}

Any ideas?


Solution

  • Take a look at the docs for ToJSONKey. Basically, the toJSONKey :: ToJSONKeyFunction a method handles two cases:

    1. when you can turn the key directly into something text like
    2. when the best you can do is turn the key into some general JSON

    For the first of these, aeson will use a proper JSON object. For the latter, it falls back to nested arrays.

    So why is it choosing option two in your case? Because you are deriving ToJSONKey and the default implementation chooses the second more general option. You can work around this problem by manually implementing ToJSONKey LOL:

    {-# LANGUAGE DeriveGeneric #-}
    {-# LANGUAGE DeriveAnyClass #-}
    
    import Data.Aeson
    import Data.Aeson.Types
    
    import qualified Data.Text as T
    import qualified Data.Map.Strict as M
    import qualified Data.ByteString.Lazy.Char8 as B
    
    import GHC.Generics
    
    data LOL = A | B | C deriving (Eq, Ord, Show, Generic, ToJSON)
    instance ToJSONKey LOL where
      toJSONKey = toJSONKeyText (T.pack . show)
    
    main = do
        B.putStrLn $ encode $ M.fromList [(A,"b")]
        B.putStrLn $ encode $ M.fromList [("A","b")]
    

    That should give you

    $ ./tojsonkey 
    {"A":"b"}
    {"A":"b"}