here is my JSON structure, there are N records that has a name
as ID to represent a children
{"Kids":
{"Jack":{"age":10}
,"Jane":{"age":9}
, .......
}
}
in the data type in Haskell
data Kid = Kid { name::String, age::Int}
instance FromJSON Kid where
parseJSON (Object v) =
....
question is ,how to make the key ( name
) as part of the constructor ? the expected output signature is like:
decode "input json string" -> [Kid]
when the expect decode function was called, it will return a list
of type Kid
. Thanks for reading this & appreciate any help .
By using the withObject
function, you get access to an Object
which is actually a KeyMap
which you can manipulate much like the usual Map
from e.g. containers
. If you're on an older aeson
version, Object
will instead be a HashMap
, so you can use that as well.
EDIT: I remember that Map
itself also has a FromJSON
, so you can probably use that instead for a shorter "solution":
{-# LANGUAGE OverloadedStrings #-}
import Data.Aeson (FromJSON(..), withObject, (.:), fromJSON)
import Data.Map (Map)
import qualified Data.Map as Map
data Kid = MkKid {name :: String, age :: Int}
newtype Kids = MkKids {unKids :: [Kid]}
instance FromJSON Kids where
parseJSON = withObject "Kids" $ \o -> do
kvmap <- o .: "Kids"
pure $ MkKids $ map (uncurry MkKid) $ Map.toList kvmap
Old "solution", which manipulates they KeyMap
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
import Data.Aeson.Key (Key)
import qualified Data.Aeson.Key as Key
import qualified Data.Aeson.KeyMap as KeyMap
import Data.Aeson.Types (Parser, Object, Value)
import Data.Aeson (FromJSON(..), withObject, (.:), fromJSON)
data Kid = MkKid {name :: String, age :: Int}
newtype Kids = MkKids {unKids :: [Kid]}
instance FromJSON Kids where
parseJSON = withObject "Kids" $ \o -> do
inner <- o .: "Kids"
withObject "inner" parseKids inner
where
parseKids :: Object -> Parser Kids
parseKids obj =
fmap MkKids $ traverse toKid $ KeyMap.toList obj
toKid :: (Key, Value) -> Parser Kid
toKid (k, v) = do
age <- parseJSON v
let name = Key.toString k
pure $ MkKid {name, age}