1

I am having problems writing a FromJSON instance to parse a JSON file containing an array of objects nested within another array of objects. The file is of the form:

[{"family":[{"name":"Jane","age":31,}
           ,{"name":"Ana","age":15,}]}
,{"family":[{"name":"Julia","age":45}
           ,{"name":"Chris","age":47}]}]

For that I created two new types:

-- A person
data Person = Person { name :: String
                     , age  :: Int} deriving (Show, Eq)

-- A family is a list of list of Person
newtype Family = Family {unFamily :: [[Person]]} deriving (Show, Eq)

And their instances are these (using XOverloadedStrings, aeson and text):

instance ToJSON Person where
  toJSON (Person n a) = object [ "name" .= n
                               , "age"  .= a]
instance FromJSON Person where
  parseJSON (Object o) = Person
    <$> o .:? "name" .!= ""
    <*> o .:? "age"  .!= 0
  parseJSON _ = mzero

instance ToJSON Family where
  toJSON (Family c) = toJSON . map (\v -> object $ ("family",v):[]) 
                             $ map toJSON c

I can make some tests like (with bytestring and maybe loaded)

> julia = Person "Julia" 45
> (== julia) . fromMaybe (Person "" 0) . decode . encode $ julia
True
> BS.putStrLn . BL.toStrict . encode $ Family [[julia,julia],[julia]]
[{"family":[{"age":45,"name":"Julia"},{"age":45,"name":"Julia"}]},{"family":[{"age":45,"name":"Julia"}]}]

and everything works as I wanted. But as I said, I couldn't write FromJSON Family, so I can't test like decode . encode as I did to julia.

I looked at the many types involved in aeson trying to find a way to make it work, but I simply couldn't do it. I could at least write toJSON for ToJSON Family because I learned that type Pair = (Text, Value).

Can anyone point me in a direction here? And is there a better way to write ToJSON Family?

2 Answers 2

2

I would write

newtype Family = Family {unFamily :: [Person]}

instead. Then

instance FromJSON Family where
    parseJSON (Object o) = Family <$> (o .: "family")
    parseJSON v = typeMismatch "family" v
instance ToJSON Family where
    toJSON (Family c) = "family" .= c

should do the trick for the instances, and the FromJSON/ToJSON instances you get for free for [Family] will decode the example JSON you showed us/encode the example family you showed us as you want.

Sign up to request clarification or add additional context in comments.

Comments

1

You can make a function that parses a sublist, and then use mapM to convert the sublists:

import Data.Foldable(toList)

instance FromJSON Family where
    parseJSON (Array arr) = Family <$> mapM parseSubList (toList arr)
        where parseSubList (Object o) = o .:? "family" .!= []

For example:

Prelude Data.Aeson Data.Foldable> decode "[{\"family\":[{\"age\":45,\"name\":\"Julia\"},{\"age\":45,\"name\":\"Julia\"}]},{\"family\":[{\"age\":45,\"name\":\"Julia\"}]}]" :: Maybe Family
Just (Family {unFamily = [[Person {name = "Julia", age = 45},Person {name = "Julia", age = 45}],[Person {name = "Julia", age = 45}]]})

1 Comment

After understanding the types used in aeson, I tried doing it something like this, but I used regular map and Vector.toList. It didn't work. In the end, monads were the answer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.