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?
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
Related
I have an json value of:
{
"name": "xyz1",
"extra": [
{
"this_string_A": "Hello"
},
{
"this_string_B": "World"
}
]
}
And a data type of:
data Abc = Abc
{ name :: String
, a :: Maybe String
, b :: Maybe String
} deriving (Generic, Show)
In the above case I would want it to parse with a result of Abc "xyz1" (Just "Hello") (Just "World").
I can't figure out how to conditionally parse the values within extra (which is a JSON array) within the aeson Parser context. How can I get extra[0].this_string_a for example? I
What I tried:
I thought I could create my own Parser (Maybe String) function but ran into confusing errors:
instance FromJSON Abc where
parseJSON = withObject "Abc" $ \v -> Abc
<$> v .: "name"
<*> myParse v
<*> myParse v
myParse :: Object -> Parser (Maybe String)
myParse x = withArray "extra" myParse2 (x)
myParse2 :: Array -> Parser (Maybe String)
myParse2 = undefined
typecheck fails with:
• Couldn't match type ‘unordered-containers-0.2.10.0:Data.HashMap.Base.HashMap
text-1.2.3.1:Data.Text.Internal.Text Value’
with ‘Value’
Expected type: Value
Actual type: Object
• In the third argument of ‘withArray’, namely ‘(x)’
And if I replace x with Object x then I get parse error of:
Left "Error in $: parsing extra failed, expected Array, but encountered Object"
Full example (run test function to test):
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
module Example where
import GHC.Generics
import Data.Aeson
import Data.Aeson.Types
data Abc = Abc
{ name :: String
, a :: Maybe String
, b :: Maybe String
} deriving (Generic, Show)
instance FromJSON Abc where
parseJSON = withObject "Abc" $ \v -> Abc
<$> v .: "name"
<*> (v.: "extra") -- find where object has key of this_string_a ??
<*> (v.: "extra") -- find where object has key of this_string_b ??
test :: Either String Abc
test = eitherDecode exampleJson
exampleJson = "{ \"name\": \"xyz1\", \"extra\": [ { \"this_string_A\": \"Hello\" }, { \"this_string_B\": \"World\" } ] }"
The withXXX "helpers" make everything kind of awkward, but here goes.
The Aeson Parser type is misnamed, and this causes confusion.
The idea with Aeson Parser objects is that they represent a monadic parse result. (This is different from the Parser objects you find in Parsec, etc., which represent actual monadic parsers.) So, you should think of a Parser a as isomorphic to an Either ParseError a -- a monadic result with the possibility of failure.
These parse results are usually combined applicatively. So if you have a parser like:
data Xyz = Xyz { x :: String, y :: String }
instance FromJSON Xyz where
parseJSON = withObject "Xyz" $ \v ->
Xyz <$> v .: "x" <*> v .: "y"
the parse results v .: "x" and v .: "y" have type Parser String which is really like Either ParseError a, and the last line of that instance is the usual method of combining successful and unsuccessful results in an applicative manner, along the lines of:
Xyz <$> Right "value_x" <*> Left "while parsing Xyz: key y was missing"
Now, the function parseJSON has type Value -> Parser a. This is what should properly be called a parser, but to avoid confusion, let's call it a "parse function". A parse function takes a JSON representation (a Value, or an Object or some other JSON thingy) and returns a parse result. The withXXX family of functions are used to adapt parse functions between JSON thingies. If you have a parse function that expects an Object, like:
\v -> Xyz <$> v .: "x" <*> v .: "y" :: Object -> Parser Xyz
and you want to adapt it to parseJSON :: Value -> Parser Xyz, you use withObject "str" :: (Object -> Parser Xyz) -> (Value -> Parser Xyz) to do it.
Getting back to your problem, if you'd like to write a core parser that looks like:
\v -> Abc <$> v .: "name" <*> extra .:? "this_string_A"
<*> extra .:? "this_string_B"
you want extra to be an Object, and you want to extract it monadically from the overall JSON object v :: Object, using appropriate withXXX helpers to adapt parse functions from one input JSON thingy type to another. So, let's write a monadic function (a parse function, in fact) to do that:
getExtra :: Object -> Parser Object
getExtra v = do
First, we monadically extract the optional "extra" component from v. We use the conditional form here, so mextra :: Maybe Value.
mextra <- v .:? "extra"
Second, let's monadically create our final Object out of "mextra". This will be the JSON Object whose keys are "this_string_A" and "this_string_B" with the array layer removed. Note the type of this case expression will be Parser Object, a parse result of type Object = HashMap key value. For the Just case, we have a Value that we expect to be an array, so let's use the withArray helper to ensure that. Note that the withArray "str" helper function takes our parse function of type \arr -> do ... :: Array -> Parser Object and adapts it to Value -> Parser Object so it can be applied to vv :: Value.
case mextra of
Just vv -> vv & withArray "Abc.extra" (\arr -> do
Now, arr is an Array = Vector Value. We hope it's an array of Objects. Let's pull the Values out as a list:
let vallst = toList arr
and then monadically traverse the list with the help of withObject to ensure they're all Objects as expected. Note the use of pure here, since we want to extract the Objects as-is without any additional processing:
objlst <- traverse (withObject "Abc.extra[..]" pure) vallst
Now, we have an objlst :: [Object]. They're a set of singleton hashmaps with disjoint keys, and the Object / hashmap we want is their union, so let's return that. The parenthesis here ends the withArray expression that's being applied to vv:
return $ HashMap.unions objlst)
For the Nothing case ("extra" not found), we merely return an empty hashmap:
Nothing -> return HashMap.empty
The full function looks like this:
getExtra :: Object -> Parser Object
getExtra v = do
mextra <- v .:? "extra"
case mextra of
Just vv -> vv & withArray "Abc.extra" (\arr -> do
let vallst = toList arr
objlst <- traverse (withObject "Abc.extra[..]" pure) vallst
return $ HashMap.unions objlst)
Nothing -> return HashMap.empty
and you use it in your parser instance like so:
instance FromJSON Abc where
parseJSON =
withObject "Abc" $ \v -> do
extra <- getExtra v
Abc <$> v .: "name" <*> extra .:? "this_string_A" <*> extra .:? "this_string_B"
With a test case:
example :: BL.ByteString
example = "{\"name\": \"xyz1\", \"extra\": [{\"this_string_A\": \"Hello\"}, {\"this_string_B\": \"World\"}]}"
main = print (eitherDecode example :: Either String Abc)
it works like so:
λ> main
Right (Abc {name = "xyz1", a = Just "Hello", b = Just "World"})
The full code:
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
import Data.Aeson (eitherDecode, FromJSON, Object, parseJSON, withArray, withObject, (.:), (.:?))
import Data.Aeson.Types (Parser)
import GHC.Generics (Generic)
import qualified Data.ByteString.Lazy as BL (ByteString)
import qualified Data.HashMap.Strict as HashMap (empty, unions)
import Data.Function ((&))
import Data.Foldable (toList)
data Abc = Abc
{ name :: String
, a :: Maybe String
, b :: Maybe String
} deriving (Generic, Show)
instance FromJSON Abc where
parseJSON =
withObject "Abc" $ \v -> do
extra <- getExtra v
Abc <$> v .: "name" <*> extra .:? "this_string_A" <*> extra .:? "this_string_B"
getExtra :: Object -> Parser Object
getExtra v = do
mextra <- v .:? "extra"
case mextra of
Just vv -> vv & withArray "Abc.extra" (\arr -> do
let vallst = toList arr
objlst <- traverse (withObject "Abc.extra[..]" pure) vallst
return $ HashMap.unions objlst)
Nothing -> return HashMap.empty
example :: BL.ByteString
example = "{\"name\": \"xyz1\", \"extra\": [{\"this_string_A\": \"Hello\"}, {\"this_string_B\": \"World\"}]}"
main = print (eitherDecode example :: Either String Abc)
Partial answer...
instance FromJSON Abc where
parseJSON = withObject "Abc" $ \v -> Abc
<$> v .: "name"
<*> (v .: "extra" >>= myParse)
<*> (v .: "extra" >>= myParse)
myParse :: Array -> Parser (Maybe String)
myParse x = withArray "extra" (lookupDictArray "this_string_a") (Array x)
lookupDictArray :: Text -> Array -> Parser (Maybe String)
lookupDictArray k a = do
let v = Vector.find (maybe False (HashMap.member k) . parseMaybe parseJSON) a
case v of
Just v' -> withObject "grrrrrrrrrrr" (\v -> v .: k) v'
Nothing -> pure Nothing
Fails to typecheck with:
src/Example.hs:32:69-77: error:
• Ambiguous type variable ‘a0’ arising from a use of
‘parseJSON’
prevents the constraint ‘(FromJSON a0)’ from being
solved.
Probable fix: use a type annotation to specify
what ‘a0’ should be.
These potential instances exist:
instance FromJSON DotNetTime
-- Defined in ‘aeson-1.4.4.0:Data.Aeson.Types.FromJSON’
instance FromJSON Value
-- Defined in ‘aeson-1.4.4.0:Data.Aeson.Types.FromJSON’
instance (FromJSON a, FromJSON b) => FromJSON
(Either a b)
-- Defined in ‘aeson-1.4.4.0:Data.Aeson.Types.FromJSON’
...plus 29 others
...plus 60 instances involving out-of-scope types
(use -fprint-potential-instances to see them all)
• In the first argument of ‘parseMaybe’, namely
‘parseJSON’
In the second argument of ‘(.)’, namely
‘parseMaybe parseJSON’
In the first argument of ‘Vector.find’, namely
‘(maybe False (member k) . parseMaybe
parseJSON)’
|
32 | let v = (Vector.find (maybe False (HashMap.member
k) . parseMaybe parseJSON) a)
decode / encode:
yDecode :: FromJSON iFromJSONable ⇒ FilePath → IO iFromJSONable
yDecode fnm = do
ymlData ← BS.readFile fnm
return $ fromMaybe (error "Can't parse from YAML") (decode ymlData)
yEncode :: ToJSON iToJSONable ⇒ FilePath → iToJSONable → IO()
yEncode fnm dat = BS.writeFile fnm $ encode dat
I create config with this encode and it creates just fine but when I'm reading it - I'm getting this error: Can't parse from YAML - on windows same code works fine and there I just can't understand what is possibly wrong?
In cases of Nothing, it's best to grab more information by using decodeEither/decodeEither'. The left side of the either value will contain an error message telling you where the failure occurs. If you switch over, you'll see that the parsing is failing due to the error "Can't parse Repository from YAML" line (see attempt1 below). It's encountering something besides an Object!
It's best then to see what the heck the YAML package is decoding to then, by decoding to the type we know that has to succeed — Value. Decoding, we get this (see attempt2 below):
Right (Array (fromList [Object (fromList [("group",Null),("branches",Array (fromList [String "master"])),("hash",Null),("clean",Null),("location",String "/home/gentoo-haskell"),("enabled",Null),("root",Null),("postRebuild",Null),("upstream",String "upstream master"),("task",String "rebase"),("positive",Null)])]))
It appears the root data structure is an Array and not an Object. There are lots of ways to fix this, and I chose a hacky one.
parseJSON (Array array) = parseJSON (array ! 0)
This makes the program work! I pasted my code below. (Apologies for the use of lens; I use it to convert between strings and bytestrings for quick scripts like these. Your program will of course work perfectly fine without it.)
{-# LANGUAGE OverloadedStrings #-}
module Lib where
import Control.Lens
import Data.ByteString
import Data.ByteString.Lens
import Data.Vector
import Data.Yaml
data Repository = Repository
{ location :: String
, task :: String
, branches :: [String]
, upstream :: String
, enabled :: Maybe Bool
, root :: Maybe Bool
, positive :: Maybe Bool
, clean :: Maybe Bool
, postRebuild :: Maybe [String]
, syncGroup :: Maybe String
, hash :: Maybe String
} deriving (Show, Eq)
instance FromJSON Repository where
parseJSON (Object v) = Repository <$>
v .: "location" <*>
v .: "task" <*>
v .: "branches" <*>
v .: "upstream" <*>
v .:? "enabled" <*>
v .:? "root" <*>
v .:? "positive" <*>
v .:? "clean" <*>
v .:? "postRebuild" <*>
v .:? "group" <*>
v .:? "hash"
parseJSON (Array array) = parseJSON (array ! 0)
raw :: String
raw = unlines [
"- group: null",
" branches:",
" - master",
" hash: null",
" clean: null",
" location: /home/gentoo-haskell",
" enabled: null",
" root: null",
" postRebuild: null",
" upstream: upstream master",
" task: rebase",
" positive: null"]
attempt1 :: Either ParseException Repository
attempt1 = decodeEither' (raw ^. packedChars)
attempt2 :: Either ParseException Value
attempt2 = decodeEither' (raw ^. packedChars)
I'm not sure if I'm barking up the wrong tree here, but I have an Aeson FromJSON definition that looks rather bulky and I was wondering if it could be turned into something more concise. I want to short-circuit the parsing of the entire object if the nested parsing of the URI fails.
data Link = Link { link :: URI
, tags :: [String]
} deriving (Show, Typeable, Eq)
instance FromJSON Link where
parseJSON :: Value -> Parser Link
parseJSON (Object o) = do
linkStr <- o .: "link"
tags' <- o .: "tags"
case parseURI linkStr of
Just l -> return $ Link l tags'
Nothing -> mzero
parseJSON _ = mzero
The type of parseURI is parseURI :: String -> Maybe URI and both Maybe and Parser have MonadPlus instances. Is there a way to compose the two directly and remove the ugly case statement at the end?
Applicative parsers are usually more concise and you can compose the result of parseURI using maybe mzero return which converts a Nothing into an mzero.
instance FromJSON Link where
parseJSON :: Value -> Parser Link
parseJSON (Object o) = Link
<$> (maybe mzero return . parseURI =<< o .: "link")
<*> o .: "tags"
parseJSON _ = mzero
Pattern matching works, but this only works inside do notation not explicit >>= due to the extra desugaring that goes on:
instance FromJSON Link where
parseJSON (Object o) = do
Just link' <- o .: "link"
tags' <- o .: "tags"
return $ Link link' tags'
parseJSON _ = mzero
> -- Note that I used link :: String for my testing instead
> decode "{\"link\": \"test\", \"tags\": []}" :: Maybe Link
Just (Link {link = "test", tags=[]})
> decode "{\"tags\": []}" :: Maybe Link
Nothing
What's going on here is that a failed pattern match on the left hand side of a <- is calling fail. Looking at the source for Parser tells me that fail is calling out to failDesc, which is also used by the implementation of mzero, so in this case you're safe. In general it just calls fail, which can do any number of things depending on the monad, but for Parser I'd say it makes sense.
However, #shang's answer is definitely better since it doesn't rely on implicit behavior.
What does the dot equals (.=) and dot colon (.:) syntax mean in this example taken from the Aeson JSON library?
instance ToJSON Coord where
toJSON (Coord xV yV) = object [ "x" .= xV,
"y" .= yV ]
-- A FromJSON instance allows us to decode a value from JSON. This
-- should match the format used by the ToJSON instance.
instance FromJSON Coord where
parseJSON (Object v) = Coord <$>
v .: "x" <*>
v .: "y"
parseJSON _ = empty
Full example on Github: https://github.com/bos/aeson/blob/master/examples/Simplest.hs
The comments are all correct. You can Hoogle for these function and find the correct haddock documentation, which shows these operators are functions defined in a library and not some integral part of the Haskell language. In general, a starting . is used by libraries to define infix functions because many other desirable symbols are either invalid for functions (ex :.) or the singular character is reserved syntax in Haskell (ex =, :).
(.=) is used to help create JSON objects while .: is for parsing JSON. Normally with Aeson you have a one-to-one match between some data structure and some set of JSON messages, so for example:
{-# LANGUAGE OverloadedStrings #-}
import Data.Aeson
import Data.Text
import Control.Applicative ((<$>), (<*>))
import Control.Monad (mzero)
data ServerMsg = ServerMsg.
{ msgId :: Int
, msg :: Text
} deriving (Show)
instance ToJSON ServerMsg where
toJSON d = object [ "msgId" .= msgId d
, "msg" .= msg d
]
instance FromJSON ServerMsg where
parseJSON (Object o) =
ServerMsg <$> o .: "msgId" <*> o .: "msg"
parseJSON _ = mzero
testJSON :: Value
testJSON = toJSON (ServerMsg 398242 "Hello user3526171")
testParse :: Result ServerMsg
testParse = fromJSON testJSON
Now you can load this module in GHCi and play around easily:
*Main> testJSON
Object fromList [("msg",String "Hello user3526171"),("msgId",Number 398242.0)]
*Main> testParse
Success (ServerMsg {msgId = 398242, msg = "Hello user3526171"})
To make long stroy short, my code is about parse a json file using aeson
Here is my two pieces of code:
a.hs
{-# LANGUAGE OverloadedStrings #-}
import Data.Aeson
import qualified Data.ByteString.Lazy.Char8 as C
import Control.Monad
import Control.Applicative
data AuctionInfo = AuctionInfo {
realm :: Realm ,
alliance :: Auctions ,
horde :: Auctions ,
neutral :: Auctions
} deriving (Show )
instance FromJSON AuctionInfo where
parseJSON (Object o) = do
r <- o .: "realm" >>= parseJSON
a <- o .: "alliance" >>= parseJSON
h <- o .: "horde" >>= parseJSON
n <- o .: "neutral" >>= parseJSON
return $ AuctionInfo r a h n
parseJSON _ = mzero
data Realm = Realm { name2 :: String , slug:: String} deriving (Show )
instance FromJSON Realm where
parseJSON (Object o) = Realm <$>
o .: "name" <*>
o .: "slug"
parseJSON _ = mzero
data Auctions = Auctions {auctions :: [Auc]} deriving (Show)
instance FromJSON Auctions where
parseJSON (Object o ) = Auctions <$> o.: "auctions"
parseJSON _ = mzero
data Auc = Auc {
auc :: Integer,
itme :: Int,
owner :: String,
bid :: Integer,
buyout ::Integer,
quantity :: Int,
timeLeft :: String,
rand :: Integer,
seed :: Integer
} deriving (Show )
instance FromJSON Auc where
parseJSON (Object o ) = Auc <$>
o .: "auc" <*>
o .: "item" <*>
o .: "owner" <*>
o .: "bid" <*>
o .: "buyout" <*>
o .: "quantity" <*>
o .: "timeLeft" <*>
o .: "rand" <*>
o .: "seed"
parseJSON _ = mzero
main = do
au<- C.readFile "a.json"
let x = decode au :: Maybe AuctionInfo
case x of
Just a -> do
{-putStrLn.show $ a-}
putStrLn .show.length.auctions.alliance $ a
putStrLn "ok"
Nothing -> putStrLn "fail"
my json test file
And test steps:
save the code , and name it a.hs (or what you want)
save the test data ,name it a.json (do not change its name)
if you have not install aeson, $ cabal install aseon
$ ghc a.hs -o a
$ ./a
What I get from the output is "fail".
And when I run the command $ runghc a.hs for a few times ,
I even got some ok and some fail mixed together.
I have also tried this code on my linux and 64bit mac ghc, they all output ok as I expected.
One of my friends has also tried this code on his 32bit mac ghc, fail too. And he told me that he played some black magic to my code and changed one line into
let x = decode $(C.pack. C.unpack) au :: Maybe AuctionInfo
then the output is ok. But when I did the same black magic, the output is still fail.
I just want to make sure is this my bug or a bug of ghc, or how can I determine that.
I'm not sure if the behaviour is related to this, but you absolutely shouldn't use Data.ByteString.Lazy.Char8 since that's only for 8-bit ASCII data and your input is UTF-8.
Try replacing that import with
import qualified Data.ByteString.Lazy as BL
and use BL.readFile to read in the data (of course the actual name doesn't matter, but BL is the idiomatic shorthand for the lazy bytestring package).
Note that usually you would use Data.Text for handling unicode text, but in this case the aeson API expects the binary (i.e. ByteString) representation and handles decoding the unicode internally.
EDIT: Actually now that I've thought about this some more, I don't think the problem is with using Char8 after all (although the point stands about not using it for unicode text in general) as you are not doing any conversions from String or Char (expect for the C.pack . C.unpack experiment, which would break all multi-byte characters).