3

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?

Sergio Losilla
  • 730
  • 1
  • 5
  • 14
  • 1
    The Generic `ToJSON` instance is almost certainly only going to produce a map when the key is text-like (ex. Text, String). Is there something wrong with making an explicit ToJSON instance? – Thomas M. DuBuisson Mar 09 '19 at 16:44
  • I see, thanks. Any hints as to why it cannot produce a map? – Sergio Losilla Mar 09 '19 at 21:27
  • Well JSON maps can't be keyed by anything besides a string. Unless you give a method to transform you key into a string that obviously won't translate. – Thomas M. DuBuisson Mar 09 '19 at 21:37
  • Sure, I was puzzled because in the encoded JSON A was being converted to text as [["A","b"]]. But yeah, now I get it from the documentation. – Sergio Losilla Mar 10 '19 at 00:17

1 Answers1

4

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"}
Alec
  • 31,829
  • 7
  • 67
  • 114