You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 06:24:30