Question

Toying a bit with the github gist API while trying to get down with the Aeson JSON library. I've run into a problem with the generated ToJSON instance, and I don't know exactly how to solve it.

I need to contain a value inside and the key that is associated to the value also needs to be a value and not a predefined key name. It's a bit easier to show. The desired output is,

{
    "public": true, 
    "description": "Something..", 
    "files": {"This Thing.md": {"content": "Here we go!"}}
}

where the value of the filename is holding the content, but currently I get,

{
    "public": true, 
    "description": "Something..", 
    "files": {"filename": "This Thing.md", "content": "Here we go!"}
}

Which isn't really what I need. The current code is,

{-# LANGUAGE OverloadedStrings, DeriveGeneric #-}
import Data.Text (Text)
import Data.Aeson
import GHC.Generics

data GistContent = GistContent
    { filename :: Text
    , content :: Text
      } deriving (Show, Generic)

instance ToJSON GistContent

data Gist = Gist
    { description :: Text
    , public      :: Bool
    , files       :: GistContent
      } deriving (Show, Generic)

instance ToJSON Gist

Under the assumption that it is possible, how would my datastructure need to look to get the desired output?.. And if that's not possible using the generics, how'd I got about it using the ToJSON instance (I can't quite figure out the structure there either)?

Was it helpful?

Solution

Your problem stems from an incorrect schema. files can currently only contain one GistContent, which is unnecessarily limiting. Instead, you'd want to have a list of GistContents:

data Gist = Gist
    { description :: Text
    , public      :: Bool
    , files       :: [GistContent]
    } deriving (Show, Generic)

Now consider another constraint on Gist: each GistContent must have a different filename. A data structure that would enforce this would be Data.HashMap.Strict.HashMap. Taking the filename out of GistContent and using the filename as a key:

data GistContent = GistContent
    { content :: Text
    } deriving (Show, Generic)

data Gist = Gist
    { description :: Text
    , public      :: Bool
    , files       :: HashMap Text GistContent
    } deriving (Show, Generic)

Everything works out.

OTHER TIPS

Here's the manually written instance (see the documentation for the class):

instance ToJSON GistContent where
   toJSON (GistContent { filename = f, content = c }) = object [f .= c]

I doubt if there would be any way to get this with your existing datatype with the automatically generated instances because all they can do is to follow the datatype using a standard scheme. Note that you can still use the generic instance for Gist because that will call the (non-generic) instance for GistContent.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top