I want to parse the following json:
{
"defaults": {
"align": "left"
},
"animals": [
{
"kind": "cat",
"name": "Oscar",
"align": "center"
},
{
"kind": "dog",
"name": "Max"
}
]
}
Parse align:
data Align = Left | Center | Right
instance FromJSON Align where
parseJSON (String "left") = pure Left
parseJSON (String "center") = pure Center
parseJSON (String "right") = pure Right
parseJSON _ = fail "Expect one of [left, center, right]."
Parse defaults:
data BlockDefaults = BlockDefaults { align :: Align }
-- default value Center if key does not exist
blockDefaults :: BlockDefaults
blockDefaults = BlockDefaults { align = Center }
instance FromJSON BlockDefaults where
parseJSON = withObject "defaults" $ \o -> BlockDefaults <$> o .:? "align" .!= align blockDefaults
Now I want to parse dog and cat.
If align
does not exist (as in dog
), it should take the value from defaults (left
).
So dog
should become Dog{name="Max", align=Center}
and cat
Cat{name="Oscar", align=Left}
.
But how do I access the default align value in parseJSON
?
-- pseudo parse code
instance FromJSON Animal where
parseJSON = withObject "animal" $ \o ->
Animal <$>
o .: "kind" <*>
o .: "name" <*>
o .:? "align" .!= <DefaultValue> -- How to access value from defaults object?
I do not want to parse defaults for every animal again, so how can I access those defaults values parsed before?
Assume there are much more values in defaults
and other animals
.
So the animal parser code looks like this now:
parseAnimal :: BlockDefaults -> Value -> Parser Animal
parseAnimal defaults = withObject "animal" $ \o ->
Animal <$>
o .: "kind" <*>
o .: "name" <*>
(
BlockDefaults <$>
o .:? "align" .!= align defaults
)
My commented suggestion was to use BlockDefaults -> Animal
as the instance. For example:
#!/usr/bin/env cabal
{- cabal:
build-depends: base, aeson
-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE FlexibleInstances #-}
import Prelude hiding (Either(..)) -- This is why you don't name your constructors 'Left' and 'Right'
import Data.Aeson
data Align = Left | Center | Right
deriving (Show)
instance FromJSON Align where
parseJSON (String "left") = pure Left
parseJSON (String "center") = pure Center
parseJSON (String "right") = pure Right
parseJSON _ = fail "Expect one of [left, center, right]."
data BlockDefaults = BlockDefaults { align :: Align }
-- default value Center if key does not exist
blockDefaults :: BlockDefaults
blockDefaults = BlockDefaults { align = Center }
instance FromJSON BlockDefaults where
parseJSON = withObject "defaults" $ \o -> BlockDefaults <$> o .:? "align" .!= align blockDefaults
data Animal = Animal { kind :: String
, name :: String
, aalign :: Align
}
deriving (Show)
instance FromJSON (BlockDefaults -> Animal) where
parseJSON = withObject "Animal" $ \o ->
do k <- o .: "kind"
n <- o .: "name"
a <- o .:? "align"
pure $ \def -> Animal k n (maybe (align def) id a)
main :: IO ()
main = print (decode "{ \"kind\": \"k\", \"name\" : \"n\" }" <*> Just blockDefaults :: Maybe Animal)