I'm using this code:
{-# LANGUAGE OverloadedStrings #-}
import Data.Aeson
import Data.Maybe
import Data.ByteString.Lazy
import Control.Applicative
import Debug.Trace
import Control.Monad
import qualified Data.Aeson.Types as T
main = do
res <- liftA show (liftA decodeOriginal (Data.ByteString.Lazy.readFile "./a.json"))
Prelude.putStrLn res
interpretResult :: Maybe String -> String
interpretResult Nothing = "Error."
interpretResult x = fromJust x
data TotalLine1 = TotalLine1 {
timestamp :: Integer,
value :: Integer
} deriving (Eq, Show)
data Original = Original {
totals :: [TotalLine1]
} deriving (Eq, Show)
instance FromJSON Original where
parseJSON (Object v) = traceStack "Original" (Original <$> (parseJSON =<< (v .: "visitors.total")))
parseJSON _ = mzero
instance FromJSON TotalLine1 where
parseJSON (Object v) = TotalLine1 <$>
v .: "timestamp" <*>
v .: "value"
decodeOriginal :: ByteString -> Maybe Original
decodeOriginal b = traceStack "decoding" (do
a <- decode b :: Maybe Original
return a)
to try and parse JSON like this:
{
visitors.total: [
{
timestamp: 1365548400,
value: 1
},
{
timestamp: 1365548700,
value: 2
},
{
timestamp: 1365549000,
value: 5
},
]
}
But main just returns Nothing every time. What have I done wrong? It seems that even parseJSON isn't being called for Original.
Your JSON file is not valid.
On the one hand, the names of the fields have to be quoted,
"timestamp"
etc. and on the other, you have a trailing comma in the list of TotalLine1s, which causes the decoding of the ByteString to a Value to fail. Quote the field names and remove the trailing comma, and it works.
Related
here is my JSON structure, there are N records that has a name as ID to represent a children
{"Kids":
{"Jack":{"age":10}
,"Jane":{"age":9}
, .......
}
}
in the data type in Haskell
data Kid = Kid { name::String, age::Int}
instance FromJSON Kid where
parseJSON (Object v) =
....
question is ,how to make the key ( name ) as part of the constructor ? the expected output signature is like:
decode "input json string" -> [Kid]
when the expect decode function was called, it will return a list of type Kid. Thanks for reading this & appreciate any help .
By using the withObject function, you get access to an Object which is actually a KeyMap which you can manipulate much like the usual Map from e.g. containers. If you're on an older aeson version, Object will instead be a HashMap, so you can use that as well.
EDIT: I remember that Map itself also has a FromJSON, so you can probably use that instead for a shorter "solution":
{-# LANGUAGE OverloadedStrings #-}
import Data.Aeson (FromJSON(..), withObject, (.:), fromJSON)
import Data.Map (Map)
import qualified Data.Map as Map
data Kid = MkKid {name :: String, age :: Int}
newtype Kids = MkKids {unKids :: [Kid]}
instance FromJSON Kids where
parseJSON = withObject "Kids" $ \o -> do
kvmap <- o .: "Kids"
pure $ MkKids $ map (uncurry MkKid) $ Map.toList kvmap
Old "solution", which manipulates they KeyMap
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
import Data.Aeson.Key (Key)
import qualified Data.Aeson.Key as Key
import qualified Data.Aeson.KeyMap as KeyMap
import Data.Aeson.Types (Parser, Object, Value)
import Data.Aeson (FromJSON(..), withObject, (.:), fromJSON)
data Kid = MkKid {name :: String, age :: Int}
newtype Kids = MkKids {unKids :: [Kid]}
instance FromJSON Kids where
parseJSON = withObject "Kids" $ \o -> do
inner <- o .: "Kids"
withObject "inner" parseKids inner
where
parseKids :: Object -> Parser Kids
parseKids obj =
fmap MkKids $ traverse toKid $ KeyMap.toList obj
toKid :: (Key, Value) -> Parser Kid
toKid (k, v) = do
age <- parseJSON v
let name = Key.toString k
pure $ MkKid {name, age}
When trying to parse some simple JSON using Aeson I get a type error I don't understand. I have the following JSON
jsonString = "[\"a\", [\"b\", \"c\"]]" :: L.ByteString
and I have defined the following imports and code:
import Data.Aeson
import GHC.Generics
import qualified Data.ByteString.Lazy as L
data Ch = Ch {
c1 :: String,
c2 :: (String, String)
} deriving (Show, Generic)
instance FromJSON Ch
When I try to use eitherDecode on this string with my Ch type I get an error
*Aeson> eitherDecode jsonString :: Either String Ch
Left "Error in $: expected record (:*:), encountered Array"
Can someone explain me the error and tell me how I should parse this JSON?
An approach that would work is
eitherDecode jsonString :: Either String (String, (String, String))
but I'd rather go to my type directly.
If you already know of a type that parses as intended then perhaps the easiest solution is to just write your instance in terms of that type and translating:
import Data.Aeson
import GHC.Generics
import qualified Data.ByteString.Lazy as L
data Ch = Ch {
c1 :: String,
c2 :: (String, String)
} deriving (Show, Generic)
instance FromJSON Ch where
parseJSON x =
do (a,(b,c)) <- parseJSON x
pure (Ch a (b,c))
And the result is:
*Main> :set -XOverloadedStrings
*Main> eitherDecode "[\"a\", [\"b\", \"c\"]]" :: Either String Ch
Right (Ch {c1 = "a", c2 = ("b","c")})
EDIT:
A more direct use of Aeson's API can be informative or preferred:
instance FromJSON Ch where
parseJSON =
withArray "Ch" $ \arr ->
-- from Data.Aeson.Types
if V.length arr /= 2
-- ^ from Data.Vector
then typeMismatch "Length should be 2" (Array arr)
-- ^ from Data.Aeson.Types
else Ch <$> parseJSON (arr ! 0) <*> parseJSON ( arr ! 1 )
I'm trying to parse JSON to produce a type with multiple constructors. The challenge is that the type is encoded in the name of a key which contains the required data. In theory I could use a bunch of .:? calls and then check if given key returns Just but I think there must be a better way. I looked at asum but this didn't help me much (probably because of my unfamiliarity with it).
import Data.Aeson
import Data.Time.Clock
data Request = Req1 { id :: String, properties :: Value }
| Req2 { id :: String, properties :: Value }
| Req3 { id :: String, time :: UTCTime }
instance FromJSON Request where
parseJSON = withObject "message" $ \o ->
-- ???
Example requests:
{"req1": {"id": "345", "p1": "v1", "p2": "v2"}}
{"req2": {"id": "654", "p3", "v3"}}
{"req3": {"id": "876", "time": 1234567890}}
Here's how to manually inspect an Object:
{-# LANGUAGE OverloadedStrings #-}
import Data.Aeson
import Data.Time.Clock
import qualified Data.HashMap.Strict as H
import Control.Monad
type Val = Int
data Request = Req1 { id :: String, properties :: Val }
| Req2 { id :: String, properties :: Val }
| Req3 { id :: String, time :: UTCTime }
instance FromJSON Request where
parseJSON (Object v) =
case H.lookup "req1" v of
Just (Object h) -> Req1 <$> h .: "id" <*> h .: "properties"
Nothing ->
case H.lookup "req2" v of
Just (Object h) -> Req2 <$> h .: "id" <*> h .: "properies"
Nothing ->
case H.lookup "req3" v of
Just (Object h) -> Req3 <$> h .: "id" <*> h .: "time"
Nothing -> mzero
If the key req1 exists it will assume it is a Req1 value; else if the key req2 exists it will try to parse it as a Req2 value; etc. for req3. If none of those keys exist it will fail.
Instead of mzero you can also use fail "..." to display a custom error message.
I need to parse an object that has a string element where the string itself is a stringified object:
{
"a" : "apples",
"bar" : "{\"b\":\"bananas\"}"
}
I would like to parse this into Just ( Foo { fooA = "apples", fooBar = Bar { barB = "bananas" } } ) so if parsing of bar returns Nothing then the parsing of the whole object returns Nothing, ie the result is as though the bar element of the object was not stringified.
Here is my attempt in which the parsing of testData returns Nothing:
{-# LANGUAGE OverloadedStrings #-}
module Main where
import Data.Aeson
import Data.Aeson.Types
data Foo = Foo { fooA :: String
, fooBar :: Bar
} deriving (Show)
instance FromJSON Foo where
parseJSON (Object o) = do bar <- (o .: "bar")
Foo <$> o .: "a" <*> parseJSON bar
parseJSON x = typeMismatch "Foo" x
data Bar = Bar { barB :: String
} deriving (Show)
instance FromJSON Bar where
parseJSON (Object o) = Bar <$> o .: "b"
parseJSON x = typeMismatch "Bar" x
testData = "{ \"a\":\"apples\", \"bar\":\"{\\\"b\\\":\\\"bananas\\\"}\" }"
main :: IO ()
main = putStrLn $ show d
where d :: Maybe Foo
d = decode testData
How can I modify the above code to perform closer to what I need?
You can get more insight into what's going on by using:
main = print (eitherDecode testData :: Either String Foo)
which displays: Left "Error in $: expected Bar, encountered String"
In this code:
parseJSON (Object o) = do bar <- (o .: "bar")
Foo <$> o .: "a" <*> parseJSON bar
bar is String ... value.
To accomplish what you want to do, you could add a case to the FromJSON instance for Bar to catch this:
instance FromJSON Bar where
...
parseJSON (String text) =
case eitherDecode (textToLBS text) of
Left e -> fail $ "while decoding a Bar: " ++ e
Right b -> return b
...
Or you could put this code in the parseJSON definition for Foo.
Here textToLBS converts strict Text to lazy ByteStrings:
import qualified Data.Text as T
import qualified Data.ByteString.Lazy as LBS
import qualified Data.Text.Encoding as TE
textToLBS :: T.Text -> LBS.ByteString
textToLBS t = LBS.fromStrict (TE.encodeUtf8 t)
Code available at: http://lpaste.net/143183
Let's say there is a JSON like:
{
"bob_id" : {
"name": "bob",
"age" : 20
},
"jack_id" : {
"name": "jack",
"age" : 25
}
}
Is it possible to parse it to [Person] with Person defined like below?
data Person = Person {
id :: Text
,name :: Text
,age :: Int
}
You cannot define an instance for [Person] literally, because aeson already includes an instance for [a], however you can create a newtype, and provide an instance for that.
Aeson also includes the instance FromJSON a => FromJSON (Map Text a), which means if aeson knows how to parse something, it knows how to parse a dict of that something.
You can define a temporary datatype resembling a value in the dict, then use the Map instance to define FromJSON PersonList, where newtype PersonList = PersonList [Person]:
data PersonInfo = PersonInfo { infoName :: Text, infoAge :: Int }
instance FromJSON PersonInfo where
parseJSON (Object v) = PersonInfo <$> v .: "name" <*> v .: "age"
parseJSON _ = mzero
data Person = Person { id :: Text, name :: Text, age :: Int }
newtype PersonList = PersonList [Person]
instance FromJSON PersonList where
parseJSON v = fmap (PersonList . map (\(id, PersonInfo name age) -> Person id name age) . M.toList) $ parseJSON v
If you enable FlexibleInstances, you can make instance for [Person]. You can parse your object to Map Text Value and then parse each element in map:
{-# LANGUAGE UnicodeSyntax, OverloadedStrings, FlexibleInstances #-}
module Person (
) where
import Data.Aeson
import Data.Aeson.Types
import Data.Text.Lazy
import Data.Text.Lazy.Encoding
import Data.Map (Map)
import qualified Data.Map as M
data Person = Person {
id ∷ Text,
name ∷ Text,
age ∷ Int }
deriving (Eq, Ord, Read, Show)
instance FromJSON [Person] where
parseJSON v = do
objs ← parseJSON v ∷ Parser (Map Text Value)
sequence [withObject "person"
(\v' → Person i <$> v' .: "name" <*> v' .: "age") obj |
(i, obj) ← M.toList objs]
test ∷ Text
test = "{\"bob_id\":{\"name\":\"bob\",\"age\":20},\"jack_id\":{\"name\":\"jack\",\"age\":25}}"
res ∷ Maybe [Person]
res = decode (encodeUtf8 test)
mniip's answer converts the JSON Object to a Map, which leads to a result list sorted by ID. If you don't need the results sorted in that fashion, it's probably better to use a more direct approach to speed things up. In particular, an Object is really just a HashMap Text Value, so we can use HashMap operations to work with it.
Note that I renamed the id field to ident, because most Haskell programmers will assume that id refers to the identity function in Prelude or to the more general identity arrow in Control.Category.
module Aes where
import Control.Applicative
import Data.Aeson
import Data.Text (Text)
import qualified Data.HashMap.Strict as HMS
data PersonInfo = PersonInfo { infoName :: Text, infoAge :: Int }
instance FromJSON PersonInfo where
-- Use mniip's definition here
data Person = Person { ident :: Text, name :: Text, age :: Int }
newtype PersonList = PersonList [Person]
instance FromJSON PersonList where
parseJSON (Object v) = PersonList <$> HMS.foldrWithKey go (pure []) v
where
go i x r = (\(PersonInfo nm ag) rest -> Person i nm ag : rest) <$>
parseJSON x <*> r
parseJSON _ = empty
Note that, like Alexander VoidEx Ruchkin's answer, this sequences the conversion from PersonInfo to Person explicitly within the Parser monad. It would therefore be easy to modify it to produce a parse error if the Person fails some sort of high-level validation. Alexander's answer also demonstrates the utility of the withObject combinator, which I'd have used if I'd known it existed.