Aeson parse JSON со значением по умолчанию из предыдущего разбора - PullRequest
1 голос
/ 09 февраля 2020

Я хочу проанализировать следующее 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 default:

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

Теперь я хочу разбирать собаку и кошку. Если align не существует (как в dog), оно должно принимать значение из значений по умолчанию (left). Так что dog должно стать Dog{name="Max", align=Center} и cat Cat{name="Oscar", align=Left}.

Но как мне получить доступ к значению выравнивания по умолчанию в 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?

Я не хочу снова проанализировать значения по умолчанию для каждого животного, так как я могу получить доступ к значениям по умолчанию, проанализированным ранее? Предположим, что в defaults и других animals.


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

parseAnimal :: BlockDefaults -> Value -> Parser Animal
parseAnimal defaults = withObject "animal" $ \o ->
  Animal <$> 
    o .: "kind" <*>
    o .: "name" <*>
    (
      BlockDefaults <$>
      o .:? "align" .!= align defaults
    )

Ответы [ 2 ]

2 голосов
/ 10 февраля 2020

Вам не нужно использовать класс FromJSON, особенно в тех ситуациях, когда синтаксический анализ может зависеть от данных времени выполнения. Благодаря свободе объявления ваших собственных анализаторов вы можете легко определить

parseAnimal :: BlockDefaults -> Value -> Parser Animal

В качестве альтернативы вы можете обобщить Animal до формы, которую можно анализировать, не зная значений по умолчанию, например, вместо этого просто поставьте Maybe. Например:

data Animal_ a
  = Animal
      Kind
      Name
      a

type Animal = Animal_ Align

parseAnimal :: Value -> Parser (Animal_ (Maybe Align))
animalWithDefault :: BlockDefaults -> Animal_ (Maybe Align) -> Animal
0 голосов
/ 19 февраля 2020

Мое прокомментированное предложение состояло в том, чтобы использовать BlockDefaults -> Animal в качестве экземпляра. Например:

#!/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)
...