Как разобрать вложенный JSON с помощью aeson
Я пытаюсь разобрать JSON следующей формы, используя aeson
{"field":{"name":"..."}}
or
{"tag":{"name":"..."}}
or
{"line":{"number":"..."}}
чтобы построить следующий тип данных
data Rule = Line Integer
| Field L.ByteString
| Tag L.ByteString
К сожалению, у меня есть две проблемы, которые я не нашел, а именно:
-
Как разобрать вложенный JSON? Рассматривая реализацию (.:), он использует поиск для извлечения определенного значения ключа. Я не решаюсь делать что-то подобное, поскольку, похоже, слишком много полагается на особенности того, как аэзон реализует вещи. Я ошибаюсь, думая, что это проблема?
-
Как использовать правильный конструктор данных на основе того, какой ключ присутствует в JSON? Все мои усилия с < | > ни к чему не привели.
Я бы опубликовал код, который я написал до сих пор, но я даже не дошел до того, что у меня есть что-то стоящее сообщение.
Ответы
Ответ 1
Как насчет следующего?
{-# 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"
Ответ 2
Для этой задачи я создал вспомогательную функцию, которая ищет ключ:
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"
и используя его две функции, которые вложены в объекты:
(.:*) :: (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
Только lookupE
зависит от внутреннего представления, поэтому его легко изменить, если это изменится. Затем {"tag":{"name":"..."}}
анализируется как v .:* ["tag", "name"]
. Обратите внимание, что он также работает для пустых списков - v .:* []
эквивалентен parseJSON v
.