3

I have the following Haskell code which encodes a list of the data type User in JSON and prints it to the standard output:

{-# LANGUAGE OverloadedStrings #-}

module Main where

import Data.Aeson
import Data.Text
import qualified Data.ByteString.Lazy.Char8 as B

data User = User
    { id :: String
    , name :: String
    , address :: String
    } deriving (Show)

instance ToJSON User where
    toJSON (User id name address) = object
        [ pack id .= object
            [ "name" .= name
            , "address" .= address
            ]
        ]

users :: [User]
users = [ User "user 1" "name of user 1" "address of user 1"
        , User "user 2" "name of user 2" "address of user 2"
        ] 

main :: IO ()
main = B.putStrLn $ encode users

At the moment, the code produces the following output:

[
  {
    "user 1": {
      "address": "address of user 1",
      "name": "name of user 1"
    }
  },
  {
    "user 2": {
      "address": "address of user 2",
      "name": "name of user 2"
    }
  }
]

However, I would like to output the following JSON structure (joining the inner two objects):

{
  "user 1": {
    "name": "name of user 1",
    "address": "address of user 1"
  },
  "user 2": {
    "name": "name of user 2",
    "address": "address of user 2"
  }
}

How will I have to change toJSON in order to print the desired encoded JSON?

watain
  • 4,838
  • 4
  • 34
  • 35
  • Not too fluent in haskell but can you get rid of the outer object wrapping the inner object? – MiltoxBeyond Mar 17 '17 at 16:10
  • Not really, because I can't just get rid of the outer object since there are two inner objects and not just one. The inner ones will first have to be merged first. – watain Mar 17 '17 at 16:35
  • 1
    Is there a typo in the queston? I see `"lastname": "name of user 2"` in the desired output but `"lastname"` does not show up anywhere else in the question. – Dave Compton Mar 17 '17 at 17:51
  • @DaveCompton, yes, sorry, I have now corrected the typo. – watain Mar 18 '17 at 00:38

1 Answers1

2

How will I have to change toJSON in order to print the desired encoded JSON?

There is nothing you can do to change toJSON for User to print desired encoded JSON. Problem is not in User encoding but in list encoding. Simple list just encoded as JSON array. And JSON array doesn't have values (so you can't have ["user 1":{...}] thus each object is wrapped into {}). This problem can be solved in different ways. One of the easiest solution is to write custom encoder for list of User. Here how it looks like:

import qualified Data.HashMap.Strict as HM

usersEncode :: [User] -> Object
usersEncode = HM.unions . map (\(Object user) -> user) . map toJSON

Adn then in main you can call it like this:

main = B.putStrLn $ encode $ usersEncode users

And it gives you desired output.

The trick is in the fact that aeson stores objects as HashMap from Text to Value. And HashMap is encoded as JSON object. So the idea of given solution is to convert each user to singleton HashMap and then unite all HashMaps.

Note: it will remove users with duplicated userId from list.

Shersh
  • 9,019
  • 3
  • 33
  • 61