Home > Net >  Deserializing JSON of the form key - value pairs using aeson
Deserializing JSON of the form key - value pairs using aeson

Time:06-27

I'm having trouble parsing JSON text in the form

{
"Person1": {
"name": "John Doe",
"job" : "Accountant",
"id": 123
},
"Person2": {
"name": "Jane Doe",
"job" : "Secretary",
"id": 321
}}

I define a new data type as follows

data Person = Person
{ name :: String
, job  :: String
, id   :: Int } deriving Show

But am having trouble defining an instance of FromJSON in this case.

Now if the JSON text were in the form of

{
 "People": [
    {
    "name": "John Doe",
    "job": "Accountant",
    "id": 123
    },
    {
    "name": "Jane Doe",
    "job": "Secretary",
    "id": 321
    }]
}

I could write

instance FromJSON person where
    parseJSON = withObject "Person" $ \obj -> Person
    <$> obj .: "name"
    <*> obj .: "job"
    <*> obj .: "id"

But I don't know how to translate this pattern to the other format.

CodePudding user response:

A JSON dictionary, such as { "foo" : "bar" } can be decoded into Maps such as Map from the containers package or HashMap from unordered-containers. Once you have your FromJSON instance for Person then just use Aeson.decode to get a Map Text Person. If you'd like to get [Person] then you could further use Data.Map.elems.

For example:

#!/usr/bin/env cabal
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{- cabal:
build-depends: base, aeson, bytestring, containers
-}

import GHC.Generics
import qualified Data.Aeson as Aeson
import Data.Map (Map)
import Data.Maybe (fromMaybe)
import qualified Data.ByteString.Lazy.Char8 as BS

data Person = Person 
        { name :: String
        , job  :: String
        , id   :: Int
        } deriving (Show, Generic, Aeson.ToJSON, Aeson.FromJSON)

main :: IO ()
main = print . decodePersons . BS.pack =<< getContents

decodePersons :: BS.ByteString -> Map String Person
decodePersons = fromMaybe (error "fail") . Aeson.decode

The interesting bit is that Aeson.decode is returning a type Map String Person. Everything is is just there to make a complete demonstration.

You can test this with the input:

% ./so.hs <<EOF
{
"Person1": {
"name": "John Doe",
"job" : "Accountant",
"id": 123
},
"Person2": {
"name": "Jane Doe",
"job" : "Secretary",
"id": 321
}}
EOF

To see output of:

fromList [("Person1",Person {name = "John Doe", job = "Accountant", id = 123}),("Person2",Person {name = "Jane Doe", job = "Secretary", id = 321})]

If you want to drop the Person1 and Person2 tags then just get the elements of the Map using Data.Map.elems.

  • Related