I am trying to parse JSON of the following form using aeson
{"field":{"name":"..."}}
or
{"tag":{"name":"..."}}
or
{"line":{"number":"..."}}
to construct the following data type
data Rule = Line Integer
| Field L.ByteString
| Tag L.ByteString
Unfortunately, I face two problems that I've not found solutions to, namely:
How do I parse nested JSON? Looking at the implementation of (.:), it uses lookup to extract a specific key's value. I'm hesitant to do something like this as it seems to be relying too much on the specifics of how aeson implements things. Am I wrong in thinking this is an issue?
How do I use the correct data constructor based on which key is present in the JSON? All my efforts with <|> have led me nowhere.
I would post the code I've written thus far, but I haven't even gotten to the point where I have anything worth posting.
How about the following?
{-# LANGUAGE OverloadedStrings #-}
import Control.Applicative
import Data.Aeson
import Data.Aeson.Types
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as L
import qualified Data.Map as M
data Rule = Line Integer
| Field L.ByteString
| Tag L.ByteString
deriving Show
instance FromJSON Rule where
parseJSON j = do
o <- parseJSON j -- takes care of JSON type check
case M.toList (o :: Object) of
[("field", Object o')] -> Field <$> o' .: "name"
[("tag", Object o')] -> Tag <$> o' .: "name"
[("line", Object o')] -> Line <$> o' .: "number"
_ -> fail "Rule: unexpected format"
For this problem I created a helper function that looks up a key:
lookupE :: Value -> Text -> Either String Value
lookupE (Object obj) key = case H.lookup key obj of
Nothing -> Left $ "key " ++ show key ++ " not present"
Just v -> Right v
loopkupE _ _ = Left $ "not an object"
and using it two functions that nest into objects:
(.:*) :: (FromJSON a) => Value -> [Text] -> Parser a
(.:*) value = parseJSON <=< foldM ((either fail return .) . lookupE) value
(.:?*) :: (FromJSON a) => Value -> [Text] -> Parser (Maybe a)
(.:?*) value = either (\_ -> return Nothing) (liftM Just . parseJSON)
. foldM lookupE value
-- Or more simply using Control.Alternative.optional
-- (.:?*) value keys = optional $ value .:* keys
Only lookupE
depends on the internal representation so it's easy to modify it, if that changes. Then {"tag":{"name":"..."}}
is parsed as v .:* ["tag", "name"]
. Note that it also works for empty lists - v .:* []
is equivalent to parseJSON v
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With