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
.