I'm having some trouble figuring out how to define FromJSON instances for an Enum type that defines a choice between two other types. My hunch is that I don't have a full enough understanding of the , <*>, and (.:) operators, as well as how the Aeson Object type works, but I haven't been able to parse apart the compilers errors yet. (Thankfully, the ToJSON instance is simple enough.)
Given two child data types, I can define instances like this:
data ChoiceSelection =
ChoiceSelection
{ csValue :: Type1 -- Type2 here also works fine
} deriving (Show,Typeable)
data Type1 =
Type1
{ t1Value :: Int
} deriving (Show,Typeable)
data Type2 =
Type2
{ t2Value :: Bool
} deriving (Show,Typeable)
instance FromJSON ChoiceSelection where
parseJSON (Object x) = ChoiceSelection
<$> (x .: "csValue")
parseJSON _ = mzero
instance FromJSON Type1 where
parseJSON (Object x) = Type1
<$> (x .: "t1Value")
parseJSON _ = mzero
instance FromJSON Type2 where
parseJSON (Object x) = Type2
<$> (x .: "t2Value")
parseJSON _ = mzero
instance ToJSON ChoiceSelection where
toJSON (ChoiceSelection value) =
object [ "csValue" .= value
]
instance ToJSON Type1 where
toJSON (Type1 value) =
object [ "t1Value" .= value
]
instance ToJSON Type2 where
toJSON (Type2 value) =
object [ "t2Value" .= value
]
This works fine, but I've been unable to define an instance for FromJSON for ExampleChoice as:
data ExampleChoice = Choice1 Type1
| Choice2 Type2
deriving (Show,Typeable)
data ChoiceSelection =
ChoiceSelection
{ csValue :: ExampleChoice
} deriving (Show,Typeable)
instance FromJSON ExampleChoice where
parseJSON (Object x) = -- ???
parseJSON _ = mzero
instance ToJSON ExampleChoice where
toJSON (Choice1 t#(Type1 _)) = toJSON t
toJSON (Choice2 t#(Type2 _)) = toJSON t
I've thought to try defining this as an msum, like so:
instance FromJSON ExampleChoice where
parseJSON (Object x) =
msum [ -- Some attempt at parsing Type1
, -- Some attempt at parsing Type2
, mzero
]
parseJSON _ = mzero
But, I haven't been able to figure out that parsing yet.
I haven't yet tried using TemplateHaskell and deriveJSON to define this for me, but even if that doesn't cause problems, I'm curious about how to solve this problem.
Edit: deriveJSON works great. I'm still curious how to build this by hand, though.
You need to change your ToJSON instance such that you can identify which data constructor to be used (I haven't tested the code but I hope this gives you the idea):
import qualified Data.HashMap.Strict as H
instance ToJSON ExampleChoice where
toJSON (Choice1 t#(Type1 _)) = object ["Choice1" .= t]
toJSON (Choice2 t#(Type2 _)) = object ["Choice2" .= t]
instance FromJSON ExampleChoice
parseJSON (Object (H.toList -> [(key, value)]))
| key == "Choice1" = Choice1 <$> parseJSON value
| key == "Choice2" = Choice2 <$> parseJSON value
parseJSON _ = fail ""
Related
I want to parse and write JSON objects that have some base attributes in common and some additional individual attributes. For example, let's say we have two types of objects User and Email. Both types share the same base attributes foo and bar, but they have additional attributes specific to their type:
User:
{"foo": "foo", "bar": "bar", "user": "me", "age": "42"}
Email:
{"foo": "foo", "bar": "bar", "email": "me#example.com"}
I have written FromJSON and ToJSON instances for the separate objects User, Email, and Base. Now my idea was to define a wrapper object combining Base and any other type with FromJSON and ToJSON instances.
data Wrapper a = Wrapper Base a
instance FromJSON a => FromJSON (Wrapper a) where
parseJSON = withObject "Wrapper" $ \v -> Wrapper <$> parseJSON (Object v) <*> parseJSON (Object v)
instance ToJSON a => ToJSON (Wrapper a) where
toJSON (Wrapper base a) = Object (toObject "base" (toJSON base) <> toObject "custom" (toJSON a))
where
toObject :: Text -> Value -> KeyMap Value
toObject _ (Object v) = v
toObject key v = KeyMap.singleton (Key.fromText key) v
toEncoding = genericToEncoding defaultOptions
The FromJSON implementations seems to work just fine. Also the toJSON function appears to pack all attributes into a single object. Unfortunately, I couldn't find a solution to merge the two Encodings together. The default toEncoding implementation packs the base and custom attributes in two separate JSON objects and merging the underlaying Builder with unsafeToEncoding doesn't help either.
Is there any aeson functionality I am missing completely or is there a much easier approach to solve my problem? Any help is appreciated. Thanks!
Update
Thanks to Daniel Wagner's answer I defined to a new typeclass ToObject and made the Wrapper data type a little more generic.
newtype Merged a b = Merged (a, b)
deriving stock (Show, Generic)
deriving newtype (Eq)
class ToObject a where
toObject :: a -> Object
toSeries :: a -> Series
instance (ToObject a, ToObject b) => ToObject (Merged a b) where
toObject (Merged (a, b)) = toObject a <> toObject b
toSeries (Merged (a, b)) = toSeries a <> toSeries b
instance (FromJSON a, FromJSON b) => FromJSON (Merged a b) where
parseJSON = Json.withObject "Merged" $ \v -> fmap Merged ((,) <$> parseJSON (Object v) <*> parseJSON (Object v))
instance (ToObject a, ToObject b) => ToJSON (Merged a b) where
toJSON = Object . toObject
toEncoding = Json.pairs . toSeries
You can build what you need using pairs and pair.
class ToObject a where toObject :: a -> Series
instance ToObject Base where
toObject b = "foo" .= foo b <> "bar" .= bar b -- but no Ken, how sad
instance ToObject User where
toObject u = "user" .= user u <> "age" .= age u
instance ToObject a => ToObject (Wrapper a) where
toObject (Wrapper base a) = toObject base <> toObject a
instance (ToObject a, ToJSON a) => ToJSON (Wrapper a) where
toJSON = -- as before
toEncoding = pairs . toObject
If I have a custom data type for parsing JSON with Aeson
data Response = Response
{ response :: [Body]
} deriving (Show)
instance FromJSON Response where
parseJSON (Object v) = Response <$> v .: "response"
parseJSON _ = mzero
data Body = Body
{ body_id :: Int
, brandId :: Int
} deriving (Show)
instance FromJSON Body where
parseJSON (Object v) = Body
<$> v .: "id"
<*> v .: "brandId"
parseJSON _ = mzero
raw :: BS.ByteString
raw = "{\"response\":[{\"id\":5977,\"brandId\":87}]}"
giving:
λ> decode raw :: Maybe Response
Just (Response {response = [Body {body_id = 5977, brandId = 87}]})
How do I derive the instances for FromJSON automatically?
I have tried:
data Response = Response
{ response :: [Body]
} deriving (Show,Generic)
data Body = Body
{ body_id :: Int
, brandId :: Int
} deriving (Show,Generic)
instance FromJSON Response
instance FromJSON Body
as has been suggested from some tutorials, but that gives:
λ> :l response.hs
[1 of 1] Compiling Response ( response.hs, interpreted )
response.hs:19:22:
Can't make a derived instance of `Generic Response':
You need DeriveGeneric to derive an instance for this class
In the data declaration for `Response'
response.hs:24:22:
Can't make a derived instance of `Generic Body':
You need DeriveGeneric to derive an instance for this class
In the data declaration for `Body'
Failed, modules loaded: none.
what am I doing wrong?
What the error is telling you is that you have to enable the DeriveGeneric extension in order for this to work. So you have to add:
{-# LANGUAGE DeriveGeneric #-}
right at the top of your file, or compile using the -XDeriveGeneric flag.
for some reason I can't wrap my head around arbitrarilly successful parses in Aeson, without making the whole system bork and cause a space leak.
Here's my issue:
newtype Foo = Foo
{ getFoo :: [(String, Maybe String)]
} deriving (Show, Eq)
instance ToJSON Foo where
toJSON (Foo xs) = object $
map (\(k,mv) -> T.pack k .= mv) xs
so far, encoding a Foo is fine and dandy. But, I want to make a parser that rejects a couple of keys, if they exist. Right now, I have a pseudo-rejection going on, and that's why I think I'm getting a bad outcome:
import qualified Data.HashMap as HM
-- the "duck-tape and chewing gum" approach
instance FromJSON Foo where
parseJSON (Object o) = Foo <$> parseJSON (Object theRest)
where
theRest = foldr HM.delete o [ "foo"
, "bar"
]
parseJSON _ = empty
This version is what caused me to think that manipulating the internal object was incorrect, because the parser may be getting "more" data in the HashMap, outside of the parser (because of the lazy bytestring being fed into it), but I am clearly not sure about this. So, I tried a different approach:
instance FromJSON Foo where
parseJSON (Object o) =
(Foo . filter (\(k,_) -> k `elem` toIgnore)) <$>
parseJSON (Object o)
where
toIgnore = ["foo", "bar"]
parseJSON _ = empty
But this also seems to cause a deadlock / space leak (not sure exactly what to diagnose this halting of execution). What would be the advised way to accept everything except a few keys of the object? I need to pattern-match on the (Object o) structure because I'm manually looking up o .: "foo" and o .: "bar" in a different component for my data type. Ideally, I would like to just remove those keys from the content and continue parsing, because I already accounted for them (hence - "the rest").
Is there any hope?
For your PartialAppContact example here is a more mundane approach which seems to work:
{-# LANGUAGE OverloadedStrings, QuasiQuotes #-}
import Data.Aeson
import qualified Data.Text as T
import qualified Data.HashMap.Strict as HM
import Control.Monad
import Text.Heredoc
type RequiredField = String
type OptionalField = String
data PartialAppContact = PartialAppContact
{ partialAppContactRequired :: [(RequiredField, String)]
, partialAppContactOptional :: [(OptionalField, Maybe String)]
} deriving (Show, Eq)
instance FromJSON PartialAppContact where
parseJSON (Object o) = do
let required = [ "firstName", "lastName", "email", "phoneNumber" ]
reqPairs <- forM required $ \k -> do
v <- o .: k
s <- parseJSON v
return (T.unpack k, s)
nonReqPairs <- forM [ (k,v) | (k,v) <- HM.toList o, k `notElem` required ] $ \(k,v) -> do
s <- parseJSON v
return (T.unpack k, s)
return $ PartialAppContact reqPairs nonReqPairs
test1 = Data.Aeson.eitherDecode "{\"firstName\":\"Athan'\"}" :: Either String PartialAppContact
input = [str|
| { "firstName": "a first name"
| , "lastName": "a last name"
| , "email": "asasd#asd.com"
| , "phoneNumber": "123-123-123"
| , "another field": "blah blah" }
|]
test2 = Data.Aeson.eitherDecode "{\"firstName\":\"Athan'\" }" :: Either String PartialAppContact
test3 = Data.Aeson.eitherDecode input :: Either String PartialAppContact
Update
Based on your comments, consider this idea for writing the instance:
import Data.List (partition)
instance FromJSON PartialAppContact where
parseJSON (Object o) = do
let required = [ "firstName", "lastName", "email", "phoneNumber" ]
let (group1, group2) = partition (\(k,_) -> k `elem` required) (HM.toList o)
reqFields <- forM group1 $ \(k,v) -> do s <- parseJSON v; return (T.unpack k, s)
otherFields <- forM group2 (\(k,v) -> (T.unpack k,) <$> parseJSON v)
return $ PartialAppContact reqFields otherFields
I found a working implementation requires the use of (.:?), to correctly implement optional, known fields. From there, you can freely decompose the HashMap and re-parseJSON it's subfields:
instance FromJSON Foo where
parseJSON (Object o) = do
mfoo <- o .:? "foo"
mbar <- o .:? "bar"
let foundFields = catMaybes [mfoo, mbar]
rest <- mapM (\(k,v) -> (T.unpack k,) <$> parseJSON v)
(toList theRest)
return $ Foo rest -- assuming you're done with `foundFields`
where
theRest = foldr HM.delete o ["foo", "bar"]
To see the final implementation of the issue discussed in the comments, see this commit.
I'm working on the I/O aspect of my json server, and there's a method I just can't get right.
First, I'll give the error, then the code and datatypes involved and some commentary about the problem afterwards.
("X-Response-Body-Start","<!DOCTYPE html>\n<html><head><title>Invalid Arguments</title></head><body><h1>Invalid Arguments</h1><ul><li>when expecting a unit constructor (U1), encountered String instead</li></ul></body></html>")
Expecting unit contructor?
Okay here's some relevant code. Let's see if we can see where I go wrong
from Datatypes.hs
data JobID = JobID Project Int deriving Generic
data Project = BNAP deriving (Show,Generic) -- one day to be an ADT
instance ToJSON Project where
toJSON = toJSON . show
instance FromJSON Project
instance FromJSON JobID
instance ToJSON JobID
The test code
testReadR :: IO Value
testReadR = do
req <- parseUrl readURI
manager <- newManager def
pBody <- runResourceT $ do
reqBody <- readObject
liftIO $ print reqBody
Response _ _ _ body <- http (buildReq req reqBody) manager
pBody <- body $$+- sinkParser json
return pBody -- (return wraps it up)
closeManager manager
return pBody
buildReq :: forall a (m :: * -> *) (t :: * -> *).
ToJSON a =>
Request t -> a -> Request m
buildReq req reqBody =
let reqBodyBS = Data.Aeson.encode reqBody
rHeaders = [(hContentType,pack "application/json")]
in req {method = fromString "POST"
, requestBody = RequestBodyLBS reqBodyBS
,requestHeaders=rHeaders
}
readObject :: ResourceT IO Value
readObject = do -- I took a bunch out because I thought simplifiying would help me
-- solve this
return $ Data.Aeson.toJSON $ JobID BNAP 306
The Handler
postReadR :: Handler RepJson
postReadR = do
conf <- parseJsonBody_ :: Handler JobID
liftIO $ print conf
testJ <- jsonToRepJson $ toJSON $ JobID BNAP 305
jValue <- jsonToRepJson conf -- to be replaced with
-- Either ErrorReport Response
-- (or something like that)
return jValue
when I change the line to
conf <- parseJsonBody_ :: Handler Value
print conf yields
Array (fromList [String "BNAP",Number 306])
So it seems the problem lies with String "BNAP" but I don't know why. Any ideas on how I can suss this out? Is there an obvious answer I'm not seeing?
Update : I have a new error. I'm sure I borked the FromJSON instance.
test: ResponseTimeout
instance FromJSON Project where
parseJSON (String p) = parseJSON $ toJSON p
parseJSON _ = mzero
The challenge here was that Project is a unary type. None of the examples I studied seemed to address that. But I know p is a Text, and toJSON can make a Value out of that, and parseJSON makes a Parser out of the Value. So it's all good right? Well, I still get the above error which is not informative at all. Any ideas?
instance ToJSON Project where
toJSON = toJSON . show
instance FromJSON Project
The instances don't work together. The generic FromJSON instance expects the generic unit constructor U1, but the ToJSON instance produces a String "BNAP".
If you have a hand-written ToJSON instance, you also need a hand-written FromJSON instance.
instance FromJSON Project where
parseJSON (String p) = parseJSON $ toJSON p
parseJSON _ = mzero
p is a Text, and we have
instance ToJSON Text where
toJSON = String
so the above instance for Project loops, since it expands parseJSON (String p) = parseJSON (String p).
For the type as it stands now,
instance FromJSON Project where
parseJSON (String "BNAP") = return BNAP
parseJSON _ = mzero
should work if you have OverloadedStrings enabled, if not,
instance FromJSON Project where
parseJSON (String p)
| p == pack "BNAP" = return BNAP
parseJSON _ = mzero
Following on from a previous post, I've found I'm totally stuck. I'm trying to parse a JSON structure into my own type, and not only am I stuck on how to parse the Array, I'm not even sure if I'm using the Aeson library as intended. Any help would be greatly appreciated.
The code:
data Exif = Exif [(T.Text, ExifValue)] deriving (Show)
data ExifValue =
ExifText T.Text |
ExifInt Integer |
ExifDouble Double |
ExifBool Bool |
ExifArray [ExifValue]
deriving (Show)
instance FromJSON ExifValue where
parseJSON (Number (I n)) = return $ ExifInt n
parseJSON (Number (D n)) = return $ ExifDouble n
parseJSON (String s) = return $ ExifText s
parseJSON (Bool b) = return $ ExifBool b
-- parseJSON (Array a) = ?????
instance FromJSON Exif where
parseJSON (Object o) = do
x <- sequence $ map f (M.assocs o)
return $ Exif x
where
f (t, x) = do
y <- parseJSON x
return ((t, y) :: (T.Text, ExifValue))
parseExifFile = fmap parseExifData . B.readFile
parseExifData :: B.ByteString -> Data.Attoparsec.Result (Data.Aeson.Result [Exif])
parseExifData content = parse (fmap fromJSON json) content
The test file:
[{
"SourceFile": "test.jpg",
"ExifTool:ExifToolVersion": 8.61,
"File:FileName": "test.jpg",
"File:FileSize": 2174179,
"File:FileModifyDate": "2011:07:27 16:53:49-07:00",
"File:FilePermissions": 644,
"File:FileType": "JPEG",
"File:MIMEType": "image/jpeg",
"File:ExifByteOrder": "MM",
"File:CurrentIPTCDigest": "32d6a77098a73aa816f2570c9472735a",
"File:ImageWidth": 2592,
"File:ImageHeight": 1936,
"File:EncodingProcess": 0,
"File:BitsPerSample": 8,
"File:ColorComponents": 3,
"File:YCbCrSubSampling": "2 2",
"XMP:Subject": ["alpha","beta","gamma"]
}]
You have to follow the type of parseJSON a little bit down a rabbit trail, but once you recognize what (Array a) represents, it should be straightforward.
parseJSON has type Value -> Parser a, so (Array a) has type Value. One of the variants in the Value type is Array Array, so the a in (Array a) must be of the type Array, which is defined as Vector Value. The Values inside that Vector are what you want to call parseJSON on to return your list, so check out what you can do with a Vector.
The easiest approach would probably to convert a to a list with Vector.toList, and then use mapM to parse the Values.
Alternately, you could avoid the Vector to list conversion by changing your ExifArray variant to hold Vector ExifValue, and then using Vector.mapM.
I'm not native english speaker, so i may not understand you very well. I guess you want to know how to parse json into recursive data type like ExifValue you presented.
So i made a simple example to show how to parse json into recursive data type.
{-# LANGUAGE OverloadedStrings #-}
import qualified Data.ByteString as B
import Data.Maybe
import Control.Monad
import Control.Applicative
import Data.Attoparsec
import Data.Attoparsec.Number
import Data.Aeson
import qualified Data.Vector as V
data Data = D1 Int | D2 [Data]
deriving (Show)
instance FromJSON Data where
parseJSON (Number (I n)) = return $ D1 $ fromInteger n
parseJSON (Array a) = D2 <$> mapM parseJSON (V.toList a)
main = do
let v = fromJust $ maybeResult $ parse json "[1,2,3,[5,3,[6,3,5]]]"
let v1 :: Data
v1 = case fromJSON v of
Success a -> a
Error s -> error s
print v1
A slightly newer build of the aeson library (0.3.2.12) supports autogenerating JSON instances.
{-# LANGUAGE TemplateHaskell #-}
import Data.Aeson
import Data.Aeson.TH (deriveJSON)
import Data.Attoparsec
import qualified Data.ByteString as B
import qualified Data.Text as T
data Exif = Exif [(T.Text, ExifValue)] deriving (Show)
data ExifValue =
ExifText T.Text |
ExifInt Integer |
ExifDouble Double |
ExifBool Bool |
ExifArray [ExifValue]
deriving (Show)
deriveJSON id ''Exif
deriveJSON id ''ExifValue
parseExifFile = fmap parseExifData . B.readFile
parseExifData :: B.ByteString -> Data.Attoparsec.Result (Data.Aeson.Result [Exif])
parseExifData content = parse (fmap fromJSON json) content
Produces:
instance ToJSON Exif where
{ toJSON
= \ value_a1Va
-> case value_a1Va of { Exif arg1_a1Vb -> toJSON arg1_a1Vb } }
instance FromJSON Exif where
{ parseJSON
= \ value_a1Vc
-> case value_a1Vc of {
arg_a1Vd -> (Exif Data.Functor.<$> parseJSON arg_a1Vd) } }
instance ToJSON ExifValue where
{ toJSON
= \ value_a1Wd
-> case value_a1Wd of {
ExifText arg1_a1We
-> object [(T.pack "ExifText" .= toJSON arg1_a1We)]
ExifInt arg1_a1Wf
-> object [(T.pack "ExifInt" .= toJSON arg1_a1Wf)]
ExifDouble arg1_a1Wg
-> object [(T.pack "ExifDouble" .= toJSON arg1_a1Wg)]
ExifBool arg1_a1Wh
-> object [(T.pack "ExifBool" .= toJSON arg1_a1Wh)]
ExifArray arg1_a1Wi
-> object [(T.pack "ExifArray" .= toJSON arg1_a1Wi)] } }
instance FromJSON ExifValue where
{ parseJSON
= \ value_a1Wj
-> case value_a1Wj of {
Object obj_a1Wk
-> case Data.Map.toList obj_a1Wk of {
[(conKey_a1Wl, conVal_a1Wm)]
-> case conKey_a1Wl of {
_ | (conKey_a1Wl == T.pack "ExifText")
-> case conVal_a1Wm of {
arg_a1Wn
-> (ExifText Data.Functor.<$> parseJSON arg_a1Wn) }
| (conKey_a1Wl == T.pack "ExifInt")
-> case conVal_a1Wm of {
arg_a1Wo
-> (ExifInt Data.Functor.<$> parseJSON arg_a1Wo) }
| (conKey_a1Wl == T.pack "ExifDouble")
-> case conVal_a1Wm of {
arg_a1Wp
-> (ExifDouble Data.Functor.<$> parseJSON arg_a1Wp) }
| (conKey_a1Wl == T.pack "ExifBool")
-> case conVal_a1Wm of {
arg_a1Wq
-> (ExifBool Data.Functor.<$> parseJSON arg_a1Wq) }
| (conKey_a1Wl == T.pack "ExifArray")
-> case conVal_a1Wm of {
arg_a1Wr
-> (ExifArray Data.Functor.<$> parseJSON arg_a1Wr) }
| otherwise -> Control.Monad.mzero }
_ -> Control.Monad.mzero }
_ -> Control.Monad.mzero } }