Подтвердить что ты не робот

Как разобрать вложенный JSON с помощью aeson

Я пытаюсь разобрать JSON следующей формы, используя aeson

{"field":{"name":"..."}}

or

{"tag":{"name":"..."}}

or

{"line":{"number":"..."}}

чтобы построить следующий тип данных

data Rule = Line Integer
          | Field L.ByteString
          | Tag L.ByteString

К сожалению, у меня есть две проблемы, которые я не нашел, а именно:

  • Как разобрать вложенный JSON? Рассматривая реализацию (.:), он использует поиск для извлечения определенного значения ключа. Я не решаюсь делать что-то подобное, поскольку, похоже, слишком много полагается на особенности того, как аэзон реализует вещи. Я ошибаюсь, думая, что это проблема?

  • Как использовать правильный конструктор данных на основе того, какой ключ присутствует в JSON? Все мои усилия с < | > ни к чему не привели.

Я бы опубликовал код, который я написал до сих пор, но я даже не дошел до того, что у меня есть что-то стоящее сообщение.

4b9b3361

Ответ 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.