'Aeson does not find a key that I believe is present

I'm trying to parse a JSON blob that looks like this:

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

Those lists of pairs of numbers are actually much longer; I've replaced their tails with ellipses.

Here's my code:

{-# 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

Here's what running demo gets me:

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\"}}"

The "asks" key looks clearly present to me -- it's the first one nested under the "order_book" key.



Solution 1:[1]

withObject "order_book" does not look into the value at key "order_book". In fact, the "order_book" argument is ignored apart from appearing in the error message; actually you should have withObject "OrderBook" there.

All withObject does is confirm that what you have is an object. Then it proceeds using that object to look for the keys "asks", "bids" and "market_id" – but the only key that's there at this level is order_book.

The solution is to only use this parser with the {"asks":[["0.06777"...]...]...} object. The "order_book" key tells no information anyway, unless there are other keys present there as well. You can represent that outer object with another Haskell type and its own FromJSON instance.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 leftaroundabout