Reputation: 1010
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
?
Upvotes: 1
Views: 836
Reputation: 153182
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.
Upvotes: 2
Reputation: 477607
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}]]})
Upvotes: 1