我有一个Haskell query函数来获得最新的令牌价格
https://coinmarketcap.com/api/documentation/v1/#operation/getV1CryptocurrencyQuotesLatest
该函数以令牌id作为arg,例如2010 for ADA。
import Data.Aeson
import Network.HTTP.Req
newtype Rate = Rate Double
query :: Int -> IO (Either Text Rate)
query tokenId =
let
url = https queryPrefix /: "v1" /: "cryptocurrency" /: "quotes" /: "latest"
idParam = "id" =: tokenId
options = standardHeader <> idParam
in
runReq defaultHttpConfig $ do
v <- req GET url NoReqBody jsonResponse options
let responseCode = responseStatusCode v
if isValidHttpResponse responseCode then do
case fromJSON $ responseBody v of
Success x -> pure $ Right x
Error e -> pure $ Left $ pack $ "Error decoding state: " <> e
else
pure $ Left $ pack ("Error with CoinMarketCap query 'Quotes Latest': " <> show responseCode <> ". " <> show (responseStatusMessage v)) 然而,Json的产出以"2010“为关键:
{"status":
{"timestamp":"2021-10-24T03:35:01.583Z","error_code":0,"error_message":null,"elapsed":163,"credit_count":1,"notice":null}
,"data":
{"2010":
{"id":2010
,"name":"Cardano"
,"symbol":"ADA"
,"slug":"cardano"
,"num_market_pairs":302,"date_added":"2017-10-01T00:00:00.000Z"
,"tags":["mineable","dpos","pos","platform","research","smart-contracts","staking","binance-smart-chain","cardano-ecosystem"]
,"max_supply":45000000000
,"circulating_supply":32904527668.666
,"total_supply":33250650235.236,"is_active":1
,"platform":null
,"cmc_rank":4
,"is_fiat":0
,"last_updated":"2021-10-24T03:33:31.000Z"
,"quote":
{"USD":
{"price":2.16109553945978
,"volume_24h":2048006882.386299
,"volume_change_24h":-24.06,"percent_change_1h":0.24896227
,"percent_change_24h":0.38920394
,"percent_change_7d":-0.97094597
,"percent_change_30d":-6.13245906
,"percent_change_60d":-21.94246757
,"percent_change_90d":63.56901345
,"market_cap":71109827972.785
,"market_cap_dominance":2.7813
,"fully_diluted_market_cap":97249299275.69,"last_updated":"2021-10-24T03:33:31.000Z"}}}}}既然2010是query的一个arg,我显然不想像这样的data.2010.quote.USD.price那样钻研:
instance FromJSON Rate where
parseJSON = withObject "Rate" $ \o -> do
dataO <- o .: "data"
_2010O <- dataO .: "2010" -- #############
quoteO <- _2010O .: "quote"
usdO <- quoteO .: "USD"
price <- usdO .: "price"
pure $ Rate price 问:我怎样才能达到我想要的灵活性?我可以以某种方式将令牌id传递给parseJSON吗?或者也许有镜头-伊索技术使用通配符?.
发布于 2021-10-24 16:06:49
我完全确信,"data"中的对象将永远只有一个键,我们可以获取对象,将其转换为一个值列表,如果列表为空或有多个值,则失败,否则继续解析。如下所示:
instance FromJSON Rate where
parseJSON = withObject "Rate" $ \o -> do
Object dataO <- o .: "data" -- we expect an Object
-- get the single value, it should be an Object itself
[Object _2010O] <- pure $ Data.Foldable.toList dataO
quoteO <- _2010O .: "quote"
usdO <- quoteO .: "USD"
price <- usdO .: "price"
pure $ Rate price 当没有键、多个键或值不是Object时,模式[Object _2010O] <-无法匹配,并通过埃森的Parser的MonadFail实例给出一个解析错误。
我们也可以更明确一点:
instance FromJSON Rate where
parseJSON = withObject "Rate" $ \o -> do
Object dataO <- o .: "data"
let objects = Data.Foldable.toList dataO
case objects of
[Object _2010O] -> do
quoteO <- _2010O .: "quote"
usdO <- quoteO .: "USD"
price <- usdO .: "price"
pure $ Rate price
[_] -> fail "value is not Object"
_ -> fail "zero or more than one key"发布于 2021-10-24 18:12:28
遗憾的是,由于我提前知道了关键名称(示例中的“2010”),所以在解析时没有使用该信息。
问题是,类型类型方法,除了它们自己的参数外,只能访问编译时已知的静态信息。tokenId很可能是运行时信息,例如从配置文件中读取。
因此,一种解决方案可以减少对FromJSON实例的依赖。不是直接解析Rate,而是先解析一个Value (Aeson的Value有一个FromJSON实例),然后在FromJSON类型集之外的函数中执行Value到Rate 解析,这个函数的作用域中有tokenId。
不过,假设我们希望在最大程度上依赖FromJSON实例。我们可以尝试“返回一个接受我们仍然不知道的数据的函数”技巧,方法是定义一个类似于
-- we need to pass the tokenId to get the to the Rate
newtype RateWoTokenId = RateWoTokenId (Text -> Result Rate) 和像这样的FromJSON实例
instance FromJSON RateWoTokenId where
parseJSON = withObject "Rate" $ \o -> do
dataO <- o .: "data"
pure $ RateWoTokenId $ \tokenId -> -- returning a function here!
-- We continue parsing inside the function,
-- because the tokenId is known there.
flip Data.Aeson.Types.parse dataO $ \dataO -> do
_2010O <- dataO .: Data.Aeson.Key.fromText tokenId
quoteO <- _2010O .: "quote"
usdO <- quoteO .: "USD"
price <- usdO .: "price"
pure $ Rate price https://stackoverflow.com/questions/69694382
复制相似问题