Question

I'm using Aeson to parse json quote data from Yahoo's API. A quote might look like this:

{
  "date": "2010-03-10",
  "Date": "2010-03-10",
  "Open": "0.37",
  "High": "0.37",
  "Low": "0.34",
  "Close": "0.35",
  "Volume": "443000",
  "Adj_Close": "0.35"
}

(that's using this YQL query)

As you can see, the numbers are quoted. I can write a fromJSON implementation like this:

instance FromJSON Quote where
  parseJSON (Object o) =
    Quote <$> o .: "Date"
          <*> o .: "Open"
          <*> o .: "High"
          <*> o .: "Low"
          <*> o .: "Close"
          <*> o .: "Volume"
  parseJSON _ = mzero

which is the same as what would be derived. Unfortunately this only works if I want Open, High, Low, etc, to be a Text type. The parse fails if I try and have any of those fields as Double, say.

I can write this:

<*> (fmap read $ o .: "Open")

to get it as anything I like, but this uses read, which is a partial function. How would I get the above functionality without using a partial function?

Was it helpful?

Solution

First, find a safe read function. I had to hoogle it.

Second, you have to use more than just applicative to get choice. In the below, I used readMay for the safe read and made a helper function to extract the fields while applying the Read instance.

{-# LANGUAGE OverloadedStrings, NoMonomorphismRestriction #-}
import Safe
import Data.Aeson
import Data.Aeson.Types (Parser)
import Data.Text as T
import Data.Word
import Control.Applicative
import Control.Monad

data Quote = Quote Text Double Double Double Double Word64
instance FromJSON Quote where
  parseJSON (Object o) = do
    let readField :: (Read a) => T.Text -> Parser a
        readField f = do
            v <- o .: f
            case readMay (T.unpack v) of
                Nothing -> fail $ "Bad Field: " ++ T.unpack f
                Just r  -> return r
    Quote <$> o .: "Date"
          <*> readField "Open"
          <*> readField "Close"
          <*> readField "Low"
          <*> readField "High"
          <*> readField "Volume"
  parseJSON _ = mzero
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top