简体   繁体   中英

How to parse nested JSON with aeson

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:

  1. 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?

  2. 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 .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM