How do I write a ToJSON/FromJSON instance for a datatype with multiple constructors? - haskell

Every example I've seen for ToJSON and FromJSON are for data types with single constructors, like so :
data RewindConfig = RConfig JobID Phase
deriving Show
instance FromJSON RewindConfig where
parseJSON (Object o) = RConfig
<$> o .: "JobID"
<*> o .: "Phase"
parseJSON _ = fail "invalid RewindConfig"
I thought I would look to how Aeson makes the instance for a type with multiple constructors, for example Either:
instance (FromJSON a, FromJSON b) => FromJSON (Either a b) where
parseJSON (Object (H.toList -> [(key, value)]))
| key == left = Left <$> parseJSON value
| key == right = Right <$> parseJSON value
parseJSON _ = fail ""
The pattern-matching in parseJSON confuses me, I don't understand what is going on with (H.toList -> [(key, value)]).
The data type I want to make instances for looks like this:
data Foo = Bar String
| Baz String
| Bin String
it did occur to me to do something I knew how to implement
data Foo = (Maybe Bar) (Maybe Baz) (Maybe Bin)
But that seems unsatisfying. Could someone help me out by explaining what's going on with the Either instance, and perhaps giving me some guidance on To/From instances for Foo?
update: I think the instances Aeson implements for Maybe are much clearer and tells me what I need to know for my needs. Still, I'd like to know what's going on with Either.

The pattern (Object (H.toList -> [(key, value)])) is called a view pattern. You can read it as something like this:
parseJSon (Object o) = case H.toList o of
[(key, value)]
| key == left -> Left <$> parseJSON value
| key == right -> Right <$> parseJSON value
It's actually slightly different, since the above will always commit to the pattern Object o when handed an Object, whereas the view pattern will only commit when both the "matches the Object o pattern" and the "H.toList o matches the [(key, value)] pattern" conditions hold, but for this example that doesn't matter.

The json package contains an encoding for data types that you might want to adopt.
If you just derive Data you can use it. It's not very fast, but very easy to use.

Assuming each datatype has a distinct key, another approach could use lenses - I like it because it's concise and is readable. For example if you have a wrapper around an A, B, and C which all have FromJSON instances:
import Data.Aeson
import Data.Maybe
import Data.Aeson.Lens
import Control.Lens
data Wrap = WrapA A | WrapB B | WrapC C
instance FromJSON Wrap where
parseJSON json
| isJust (json ^? key "A's unique key") = WrapA <$> parseJSON json
| isJust (json ^? key "B's unique key") = WrapB <$> parseJSON json
| isJust (json ^? key "C's unique key") = WrapC <$> parseJSON json
| otherwise = fail "Bad message"

Related

How to parse a JSON string using Aeson that can be one of two different types

I'm currently struggling to parse some JSON data using the aeson library. There are a number of properties that have the value false when the data for that property is absent. So if the property's value is typically an array of integers and there happens to be no data for that property, instead of providing an empty array or null, the value is false. (The way that this data is structured isn't my doing so I'll have to work with it somehow.)
Ideally, I would like to end up with an empty list in cases where the value is a boolean. I've created a small test case below for demonstration. Because my Group data constructor expects a list, it fails to parse when it encounters false.
data Group = Group [Int] deriving (Eq, Show)
jsonData1 :: ByteString
jsonData1 = [r|
{
"group" : [1, 2, 4]
}
|]
jsonData2 :: ByteString
jsonData2 = [r|
{
"group" : false
}
|]
instance FromJSON Group where
parseJSON = withObject "group" $ \g -> do
items <- g .:? "group" .!= []
return $ Group items
test1 :: Either String Group
test1 = eitherDecode jsonData1
-- returns "Right (Group [1,2,4])"
test2 :: Either String Group
test2 = eitherDecode jsonData2
-- returns "Left \"Error in $.group: expected [a], encountered Boolean\""
I was initially hoping that the (.!=) operator would allow it to default to an empty list but that only works if the property is absent altogether or null. If it were "group": null, it would parse successfully and I would get Right (Group []).
Any advice for how to get it to successfully parse and return an empty list in these cases where it's false?
One way to solve this problem is to pattern match on the JSON data constructors that are valid for your dataset and raise invalid for all others.
For instance, you could write something like this for that particular field, keeping in mind that parseJSON is a function from Value -> Parser a:
instance FromJSON Group where
parseJSON (Bool False) = Group <$> pure []
parseJSON (Array arr) = pure (Group $ parseListOfInt arr)
parseJSON invalid = typeMismatch "Group" invalid
parseListOfInt :: Vector Value -> [Int]
parseListOfInt = undefined -- build this function
You can see an example of this in the Aeson docs, which are pretty good (but you kind of have to read them closely and a few times through).
I would probably then define a separate record to represent the top-level object that this key comes in and rely on generic deriving, but others may have a better suggestion there:
data GroupObj = GroupObj { group :: Group } deriving (Eq, Show)
instance FromJSON GroupObj
One thing to always keep in mind when working with Aeson are the core constructors (of which there are only 6) and the underlying data structures (HashMap for Object and Vector for Array, for instance).
For example, in the above, when you pattern match on Array arr, you have to be aware that you're getting a Vector Value there in arr and we still have some work to do to turn this into a list of integers, which is why I left that other function parseListOfInt undefined up above because I think it's probably a good exercise to build it?

Haskell aeson ParseJSON example using (.:)

There is the following snippet of code in the Aeson package usage example:
data Coord = Coord { x :: Double, y :: Double }
instance FromJSON Coord where
parseJSON (Object v) = Coord <$>
v .: "x" <*>
v .: "y"
The type of parseJSON function is parseJSON :: Value -> Parser a.
I have the following question about this code: what is the .: function? From the example I might say that its type is Object -> String -> Parser String, however I can't find anything about it on hoogle/hackage. Any help would be appreciated!
It retrieves the value associated with the key. (.:) produces a parse failure (via empty from Alternative) if the key isn't there, so it is suitable for mandatory keys (as opposed to (.:?), which makes sense for optional ones).

How does `instance FromJSON a => FromJSON (Entity a)` work in Haskell?

I am new to Haskell. I am trying to create a simple JSON API client, and have found one implemented for Twitter in Haskell. My current goal is outlined in this question, but the same thing is demonstrated below.
In that Twitter/Haskell API code, there is this snippet:
https://github.com/himura/twitter-types/blob/master/Web/Twitter/Types.hs#L577-L587
type EntityIndices = [Int]
data Entity a = Entity {
entityBody :: a, -- ^ The detail information of the specific entity types (HashTag, URL, User)
entityIndices :: EntityIndices, -- ^ The character positions the Entity was extracted from
} deriving (Show, Eq)
instance FromJSON a => FromJSON (Entity a) where
parseJSON v#(Object o) = Entity <$> parseJSON v
<*> o .: "indices"
parseJSON _ = mzero
What is happening here?
First, from my understanding, That data block is a Generalized Algebraic Data Type, because you are passing a parameter into the type data Entity a, and that a is being used in entityBody :: a.
Second, how do you instantiate that Entity generalized algebraic data type?
Finally, what is happening here?
instance FromJSON a => FromJSON (Entity a) where
What does that => mean?
I can break this into multiple questions if that helps, but it all seems sorta interconnected.
The definition for Entity
data Entity a = Entity {
entityBody :: a, -- ^ The detail information of the specific entity types (HashTag, URL, User)
entityIndices :: EntityIndices, -- ^ The character positions the Entity was extracted from
} deriving (Show, Eq)
a can be of any type. There are no restrictions. The instance for FromJSON for Entity puts a restraint on the type of a. It is saying this instance of FromJSON for Entity the a type must also have an instance of FromJSON defined.
For example the show typeclass.
show :: Show a => a -> String
In order to call the show function the argument passed in a must have an instance of Show. The => just separates the typeclass constraints for the type definition.
So back to the FromJSON. If you define your own data type.
data Person = Person { name :: String }
And write the code
let e = eitherDecode data :: Either String (Entity Person)
It won't compile because you haven't defined an instance of FromJSON for Person. If you create the instance then it will work.
instance FromJSON Person where
parseJSON (Object o) = Person <$> o .: "name"
parseJSON _ = mzero

parseJSON class method with dependent type

and thanks in advance for the help :)
Here's the problem I'm trying to solve:
I have a type (MyType) and I wrote a JSON parser for it (Using aeson library), and this parser depends on another value (Config):
import Data.Aeson
data MyType = MyType Text
data Config = Config Text
parseMyType :: Config -> Value -> Parser MyType
parseMyType (Config f) (Object o) = do (String v) <- o .: f
return $ MyType v
What I really wanted to write a FromJSON instance for it... but the parseJSON only depends on the Value (cannot have Config):
instance FromJSON MyType where
parseJSON :: Value -> Parser MyType
parseJSON = ???
I'm wondering if it's possible to use type class in this case. I'm probably missing some type trick... or maybe there's a language extension I can use?
Thank you!
If you want to "magic up" an instance declaration based on runtime information, this is possible through the reflection library, though it is some rather deep magic and there is probably a nicer way: https://www.fpcomplete.com/user/thoughtpolice/using-reflection

Proxies, type level symbols, and JSON

I'm trying to add automagical json parsing to Data.Vinyl
Here is an instance for FromJSON for records with exactly one element.
It almost works, but I can't satisfy the KnownSymbol constraint, it seems to auto generate a new type variable on me.
instance (KnownSymbol sym, FromJSON a) => FromJSON (PlainRec '[ sym ::: a ]) where
parseJSON (Object v) = (field =:) <$> (v .: json_name)
where field = Field :: (sym ::: a)
json_name = T.pack $ show field
The error is
Could not deduce (KnownSymbol sym0) arising from a use of ‛show’
from the context (KnownSymbol sym, FromJSON a)
More context http://lpaste.net/101005
If I replace all instances of sym with "name", it works, and runs and it is wonderful. Now, I could use template Haskell to generate all the instances ahead of time, since I have a closed list of field names that I'll actually use, but that seems like such a shame. I know next to nothing about Data.Proxy, having just seen in used to define the show instance for the records of Data.Proxy.
You just have to enable ScopedTypeVariables.

Resources