Aeson 没有找到我认为存在的钥匙

Aeson does not find a key that I believe is present

我正在尝试解析如下所示的 JSON blob:

"{\"order_book\":{\"asks\":[[\"0.06777\",\"0.00006744\"],[\"0.06778\",\"0.01475361\"], ... ]],\"bids\":[[\"0.06744491\",\"1.35\"],[\"0.06726258\",\"0.148585363\"], ...]],\"market_id\":\"ETH-BTC\"}}"

那些数字对的列表实际上要长得多;我用省略号替换了它们的尾巴。

这是我的代码:

{-# LANGUAGE OverloadedStrings #-}

module Demo where

import Data.Aeson
import Data.ByteString.Lazy hiding (putStrLn)
import Data.Either (fromLeft)
import Network.HTTP.Request

data OrderBook = OrderBook
  { orderBook_asks     :: [[(Float,Float)]]
  , orderBook_bids     :: [[(Float,Float)]]
  , orderBook_marketId :: String
  }

instance FromJSON OrderBook where
  parseJSON = withObject "order_book" $ \v -> OrderBook
    <$> v .: "asks"
    <*> v .: "bids"
    <*> v .: "market_id"

demo :: IO ()
demo = do
  r <- get "https://www.buda.com/api/v2/markets/eth-btc/order_book"
  let d = eitherDecode $ fromStrict $ responseBody r :: Either String OrderBook
  putStrLn $ "Here's the parse error:"
  putStrLn $ fromLeft undefined d
  putStrLn $ "\n\nAnd here's the data:"
  putStrLn $ show $ responseBody r

这是 运行 demo 让我明白的:

Here's the parse error:
Error in $: key "asks" not found

And here's the data:
"{\"order_book\":{\"asks\":[[\"0.06777\",\"0.00006744\"],[\"0.06778\",\"0.01475361\"], ... ]],\"bids\":[[\"0.06744491\",\"1.35\"],[\"0.06726258\",\"0.148585363\"], ...]],\"market_id\":\"ETH-BTC\"}}"

“询问”键看起来很清楚——它是第一个嵌套在“order_book”键下的键。

密钥存在,但它被包裹在另一个嵌套对象中,因此您必须先打开外部对象才能解析密钥。

执行此操作的 smallest-diff 方法可能只是内联:

instance FromJSON OrderBook where
  parseJSON = withObject "order_book" $ \outer -> do
    v <- outer .: "order_book"
    OrderBook
      <$> v .: "asks"
      <*> v .: "bids"
      <*> v .: "market_id"

尽管您可能想考虑引入另一种包装类型。这实际上取决于您拥有的数据格式的语义。


我猜你可能假设这是 withObject "order_book" 会做的,但事实并非如此。 withObject 的第一个参数只是被解析对象的 human-readable 名称,用于创建错误消息。通常该参数应命名正在解析的 type - 即 withObject "OrderBook"See the docs.


另外,我认为您的 asksbids 字段输入错误。

首先,您的 JSON 输入看起来应该是元组数组,但您的 Haskell 类型表示 双重嵌套 元组数组。所以这将无法解析。

其次,您的 JSON 输入包含字符串作为这些元组的元素,但您的 Haskell 类型表示 Float。这也将无法解析。

根据您的 JSON 输入,正确的类型应该是:

  { orderBook_asks     :: [(String,String)]
  , orderBook_bids     :: [(String,String)]

或者,如果您真的想要浮点数,则必须从字符串中解析它们:

instance FromJSON OrderBook where
  parseJSON = withObject "order_book" $ \outer -> do
    v <- outer .: "order_book"
    OrderBook
      <$> (map parseTuple <$> v .: "asks")
      <*> (map parseTuple <$> v .: "bids")
      <*> v .: "market_id"
    where
      parseTuple (a, b) = (read a, read b)

(注意这个 ☝️ 代码不能被复制和粘贴:我正在使用 read 将字符串解析为浮点数,如果字符串格式不正确,它会在运行时崩溃;在实际程序中你应该使用更好的解析方式)

withObject "order_book" 不查看键 "order_book" 处的值。事实上, "order_book" 参数除了出现在错误消息中外,被忽略了;实际上你应该有 withObject "OrderBook" 那里。

所有 withObject 所做的就是确认您拥有的 一个对象。然后它继续使用该对象查找键 "asks""bids""market_id" – 但在这一层唯一的键是 order_book.

解决方案是仅将此解析器与 {"asks":[["0.06777"...]...]...} 对象一起使用。 "order_book" 键无论如何都不会告诉任何信息,除非那里也存在其他键。您可以用另一个 Haskell 类型和它自己的 FromJSON 实例来表示该外部对象。