Haskell如何建模固定外层键与枚举内层键的嵌套JSON?
处理多货币API的Aeson类型建模方案
问题背景
现有一个外部API,传入usd或eur会返回对应结构的嵌套JSON:
api currency = case currency of "usd" -> "{\"bitcoin\": {\"usd\": 20403}, \"ethereum\": {\"usd\": 1138.75}}" "eur" -> "{\"bitcoin\": {\"eur\": 20245}, \"ethereum\": {\"eur\": 1129.34}}"
单处理usd时可以用Aeson泛型解码实现,但要同时兼容两种货币,既不想用过于泛化的嵌套Map丢失类型安全,又要保留bitcoin/ethereum这些固定外层键,还要最大化利用自动解码、避免手写fromJSON,之前尝试参数化Currency类型没成功,下面是可行的建模方案:
最优实现方案:参数化类型+自定义字段映射
通过参数化类型标记货币,结合Aeson的Options调整字段名映射,既保留类型安全,又复用泛型解码能力:
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE TypeApplications #-} import Data.Aeson import Data.Aeson.Types import GHC.Generics -- 参数化价格类型,a是货币标记类型 data Price a = Price { price :: Double } deriving (Show, Generic) -- 参数化加密货币类型,固定bitcoin/ethereum键 data Coin a = Coin { bitcoin :: Price a , ethereum :: Price a } deriving (Show, Generic) -- 定义货币标记空类型,用于区分不同货币 data USD = USD deriving (Show) data EUR = EUR deriving (Show) -- 为USD价格实例化FromJSON,指定JSON字段名为"usd" instance FromJSON (Price USD) where parseJSON = genericParseJSON defaultOptions { fieldLabelModifier = const "usd" } instance FromJSON (Coin USD) where parseJSON = genericParseJSON defaultOptions -- 为EUR价格实例化FromJSON,指定JSON字段名为"eur" instance FromJSON (Price EUR) where parseJSON = genericParseJSON defaultOptions { fieldLabelModifier = const "eur" } instance FromJSON (Coin EUR) where parseJSON = genericParseJSON defaultOptions -- 统一处理函数,根据输入货币返回对应类型的结果 processCurrency :: String -> Maybe (Either (Coin USD) (Coin EUR)) processCurrency "usd" = Left <$> decode (api "usd") processCurrency "eur" = Right <$> decode (api "eur") processCurrency _ = Nothing -- 原API函数(保持不变) api currency = case currency of "usd" -> "{\"bitcoin\": {\"usd\": 20403}, \"ethereum\": {\"usd\": 1138.75}}" "eur" -> "{\"bitcoin\": {\"eur\": 20245}, \"ethereum\": {\"eur\": 1129.34}}"
方案优势
- 保留了
bitcoin/ethereum的固定键约束,不会出现非法键的情况 - 每个货币对应明确的类型(
Coin USD/Coin EUR),类型安全,编译期就能发现错误 - 完全复用Aeson泛型解码能力,仅需调整字段名映射,不需要手写复杂的
parseJSON逻辑 - 扩展新货币时,只需要新增对应的标记类型和
FromJSON实例即可
内容的提问来源于stack exchange,提问作者cobra
相关产品推荐
相关产品推荐

