Haskell Aeson JSON, отфильтровать недопустимые символы

Использование Haskell с Aeson JSON Hackage и следующий JSON:

{
    "base": "GBP",
    "date": "2017-10-27",
    "rates": {
        "#USD": 1.3093,
        "#EUR": 1.1282
    }
}

Как лучше всего реализовать экземпляр FromJson?

В настоящее время у меня есть это:

{-# LANGUAGE OverloadedStrings, DeriveGeneric #-}

import GHC.Generics
import Data.Aeson

data Conversion = Conversion {
  base :: String,
  rates  :: Rates }
  deriving (Show, Generic)

data Rates = Rates {
  eur :: Float,
  usd :: Float }
  deriving (Show, Generic)

instance FromJSON Conversion
instance FromJSON Rates where
  parseJSON (Object o) = trace ( show(o)) Rates <$> o .: "#USD" <*> o .: "#EUR"

Я определил обе возможности в файле instance FromJSON Rates. Я попытался сделать это более общим способом, но «недопустимые» символы # не разрешены в части data.

Так что в этом случае у меня есть только два раздражающих поля. Но если я хочу расширить это и получить несколько надоедливых символов (#, @, - и т. д.), должен ли я определять каждое поле? Или есть более умный и быстрый способ добиться того же?


person Viletung    schedule 30.10.2017    source источник
comment
Я не могу воспроизвести какую-либо ошибку - я пробовал в ghci (+overloadedstrings) let x :: Maybe Conversion = decode "{\"base\":\"base\",\"rates\":{\"#USD\":1,\"#EUR\":2}}" с вашими экземплярами.   -  person epsilonhalbe    schedule 30.10.2017
comment
если у вас нестандартный json - вам придется написать объявление экземпляра parseJSON самостоятельно. Если вы сами определяете json, я бы воздержался от добавления странных средств доступа к полям, таких как # @ и т. д., и придерживался обычного нижнего регистра с синтаксисом тире.   -  person epsilonhalbe    schedule 30.10.2017
comment
@epsilonhalbe абсолютно верно. К сожалению, голландский сайт прогноза погоды использует странные методы доступа к полям. Использование ответа, данного Матеушем Ковальчиком, решает мою проблему, всем спасибо :)   -  person Viletung    schedule 31.10.2017


Ответы (1)


Вы можете справиться с этим, используя fieldLabelModifier и заменив проблемные поля своими. Это позволяет вам выбирать, какие имена изменять, что очень полезно, если у вас есть большие записи с несколькими полями со странными именами, которые вы не можете напрямую поместить в свой тип.

{-# LANGUAGE DeriveGeneric     #-}
{-# LANGUAGE LambdaCase        #-}
{-# LANGUAGE OverloadedStrings #-}
module Main (main) where

import           Data.Aeson
import           Data.Aeson.Types
import qualified Data.ByteString.Lazy as BSL
import qualified Data.Map.Strict as M
import           GHC.Generics
import           System.Environment (getArgs)

data Conversion = Conversion
  { base :: String
  , rates  :: Rates
  } deriving (Show, Generic)

newtype USD = USD Float
newtype EUR = EUR Float

data Rates = Rates
  { eur :: Float
  , usd :: Float
  }
  deriving (Show, Generic)

instance FromJSON Conversion

instance FromJSON Rates where
  parseJSON = genericParseJSON opts
    where
      fields = M.fromList
        [("usd", "#USD"), ("eur", "#EUR")]
      opts = defaultOptions
        { fieldLabelModifier = \s -> M.findWithDefault s s fields }

main :: IO ()
main = do
  [file] <- getArgs
  decode <$> BSL.readFile file >>= \case
    Nothing -> putStrLn "Parse failed!"
    Just conversion -> print (conversion :: Conversion)

С этим мы получаем

[nix-shell:/tmp]$ ./T /tmp/rates.json
Conversion {base = "GBP", rates = Rates {eur = 1.1282, usd = 1.3093}}

[nix-shell:/tmp]$ cat /tmp/rates.json
{
  "base": "GBP",
  "date": "2017-10-27",
  "rates": {
    "#USD": 1.3093,
    "#EUR": 1.1282
  }
}

Просто не забудьте использовать те же параметры Aeson, если вы когда-либо определяли экземпляр ToJSON для своего типа!

person Mateusz Kowalczyk    schedule 30.10.2017