繁体   English   中英

Haskell:如何使用固定的外键和枚举的内键对嵌套 JSON 建模?

[英]Haskell: How do I model a nested JSON with fixed outer keys and an enumerated inner key?

考虑一个将usdeur作为输入的外部 API,并相应地返回一个 json,如下所示:

api currency = case currency of
  "usd" -> "{\"bitcoin\": {\"usd\": 20403}, \"ethereum\": {\"usd\": 1138.75}}"
  "eur" -> "{\"bitcoin\": {\"eur\": 20245}, \"ethereum\": {\"eur\": 1129.34}}"

如果我只需要api "usd" ,我会使用 Aeson 的 (?) 通用解码功能:

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DeriveGeneric #-}

import Data.Aeson
import GHC.Generics
  
data Usd = Usd 
  { usd :: Double
  } deriving (Show, Generic)
instance FromJSON Usd

data Coin = Coin
  { bitcoin :: Usd
  , ethereum :: Usd
  } deriving (Show,Generic)
instance FromJSON Coin

processUsd = decode (api "usd") :: Maybe CoinUsd

但是如果要同时使用api "usd"api "eur" ,那么抽象currency的最佳方法是什么?

(如果你问我真的想用它做什么,那么答案是什么!这个例子是人为的。我想了解在建模 json 格式时使用dataclass的方法,它的键在某些方面受到约束。我也想最大限度地利用Aeson的自动解码功能,尽量避免自定义fromJSON代码。)

一种选择是使用嵌套Data.Map

processAny :: String -> Maybe (M.Map String (M.Map String Double)) 
processAny currency = decode (api currency)  

但这太笼统了。 我仍然想要硬编码/固定外部键( "bitcoin"等)。 在这种挑剔程度下有哪些选择? 我的直接想法是拥有一个通用的Currency类型并将其用作Coin的参数。 但我不知道如何解决它?! 以下是一些模糊的陈述,我希望能传达我的意图:

data (Currency a) => Coin a
  { bitcoin :: a
  , ethereum :: a
  } deriving (Show,Generic)
instance FromJSON (Coin a) where
  -- parseJSON x = codeIfNeeded

class (FromJSON a) => Currency a where
  -- somehow abstract out {currencyName :: Double} ?!

我什至不确定它是否有任何意义,但如果有,我该如何正式化它? 此外,其他建模的最佳方法是什么(同时,如前所述,不诉诸极端的Data.Map和完全手写的parseJSON )?

让我们首先对像{"usd": 20403}这样的元素进行单独建模。 我们可以定义一个类型

{-# LANGUAGE DerivingStrategies #-}
newtype CurrencyAmount currency = CurrencyAmount {getCurrencyAmount :: Double}
  deriving stock (Show)

用“幻像类型”参数化,如:

data Euro  -- no constructors required, used only as type-level info

data USD

这种方法让我们(并迫使我们)为不同的货币重用相同的“实现”和操作。

我们要做的一项操作是解析“标记”的货币金额。 但 JSON 中的 key 因每种货币而异,即取决于 phantom 类型。 如何解决这个问题?

Haskell 中的类型类让我们从类型中获取值。 因此,让我们编写一个类型类,为我们提供用于每种货币的 JSON Key

import Data.Aeson
import Data.Aeson.Key
import Data.Proxy

class Currency currency where
  currencyKey :: Proxy currency -> Key -- Proxy optional with AllowAmbiguousTypes

有实例

{-# LANGUAGE OverloadedStrings #-}
instance Currency Euro where
  currencyKey _ = "eur"

instance Currency USD where
  currencyKey _ = "usd"

现在我们可以为CurrencyAmount编写一个显式的FromJSON实例:

instance Currency currency => FromJSON (CurrencyAmount currency) where
  parseJSON = withObject "amount" $ \o ->
    CurrencyAmount <$> o .: currencyKey (Proxy @currency)

我们可以这样定义Coin

{-# LANGUAGE DeriveAnyClass #-}
data Coin currency = Coin
  { bitcoin :: CurrencyAmount currency,
    ethereum :: CurrencyAmount currency
  }
  deriving stock (Show, Generic)
  deriving anyclass (FromJSON)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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