Haskell ADTs with aeson - haskell

I've been fighting with a simple ADT, trying to get it to round-trip back and forth to JSON, but I've had no luck, no matter how I try to massage or modify the type. What am I missing?
When it compiles, I always get the same runtime error:
> let t = Fahrenheit
> fromJSON $ toJSON t
Error "when expecting a (), encountered Object instead"
Trying this just gives me "Nothing", presumably because of the same error: decode $ encode t
I've tried to follow these sources, but I can't seem to get around this runtime error, no matter what I try:
Haskell :: Aeson :: parse ADT based on field value
https://www.fpcomplete.com/user/Geraldus/algebraic-data-types-adts-with-aeson
Here's one form of the code I'm using. At first, I tried to use this as a type embedded in another type, but when that didn't work I added the "value" key to try to make parsing of this easier (no luck).
data TemperatureType = Celsius
| Fahrenheit
deriving (Show,Read,Typeable,Data,Eq)
-- This doesn't work either
-- $(deriveJSON defaultOptions ''TemperatureType)
instance ToJSON TemperatureType where
toJSON Fahrenheit = object [ "value" .= String "Fahrenheit" ]
toJSON Celsius = object [ "value" .= String "Celsius" ]
instance FromJSON TemperatureType where
parseJSON (Object x) = toTemperatureType <$> x .: "value"
toTemperatureType :: Text -> TemperatureType
toTemperatureType "Fahrenheit" = Fahrenheit
toTemperatureType "Celsius" = Celsius

Haskell need help from you about the type of your expression result since in the current call it's not possible to infer it:
> fromJSON $ toJSON t :: Result TemperatureType

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?

How do I get rid of "Defaulting the following constraints to type ‘[Char]’" warnings?

I'm using the Aeson library and the OverloadedStrings pragma, but this question is more general. The immediate context is that I'm writing code for a sum type so I have a "kind" field in the JSON to say which variant is being represented. In the ToJSON instance I write code like this:
toJSON (Foo v) = ["kind" .= "foo" ...]
toJSON (Bar v) = ["kind" .= "bar" ...]
GHC gives me the following warning for "foo" and "bar":
Defaulting the following constraints to type ‘[Char]’
(ToJSON v0)
arising from a use of ‘.=’
The reason is that both String and Text are instances of ToJSON, so the compiler has to pick one, and in this case it has picked String.
I know I can suppress the warning with an in-line type, like this:
toJSON (Foo v) = ["kind" := ("foo" :: Text) ...]
However that clutters up the code with redundant information. Also I've got quite a lot of these warnings.
I've tried putting default (Text) at the top of the file, but that just changes the warning to tell me its defaulted to type Text.
Is there a way of disabling the warnings for Text/String defaults but leaving any others in place?
Why not lift that part of the serialisation logic out into a separate function? It's kind of hacky anyway, so better to section it off. And you'll silence the warning (provided you give the function a type).
Something along the lines of...
toJSON thing#(Foo v) = ["kind" .= kind thing ...]
toJSON thing#(Bar v) = ["kind" .= kind thing ...]
kind :: T -> Text
kind (Foo _) = "foo"
kind (Bar_) = "bar"
However you want to to do it. Maybe use a ViewPattern or maybe make kind :: KeyValue kv => T -> kv. Just don't inline it, IMO.
In the end I took Thomas DuBuisson's suggestion from the comments and added a couple of specialized functions:
-- | Type-restricted version of "(.=)" to silence compiler warnings about defaults.
(..=) :: (KeyValue kv) => Text -> Text -> kv
(..=) = (.=)
-- | Type restricted version of "(.:)" to silence compiler warnings about defaults.
(..:) :: Object -> Text -> Parser Text
(..:) = (.:)
I think I'd probably abstract the key-value pair you're making, like this:
kind :: KeyValue kv => Text -> kv
kind lbl = "kind" .= lbl
toJSON (Foo v) = [kind "foo", ...]
toJSON (Bar v) = [kind "bar", ...]

Parse top-level value with Aeson

I'm trying to parse JSON values with Aeson and I have no problem (so far) parsing objects or arrays, but I can't get Aeson to parse JSON documents that are just strings.
As I understand, since RFC 7159 values are legal JSON documents, and Aeson supports that since 0.9.0.0 (I'm using 0.9.0.1), so it should work. For example, I'm wrapping an API that returns strings as top-level JSON documents for many of its calls, and would like to newtype those strings for some static typing safety:
newtype Bar = Bar String deriving (Eq, Show)
instance FromJSON Bar where
parseJSON (String v) = pure (Bar $ T.unpack v)
parseJSON _ = mzero
If I try to decode something:
decode "JustSomeRandomString" :: Maybe Bar
all I get is Nothing in return.
Any ideas what I'm doing wrong? Of course, I could handle API calls that return strings as JSON documents without Aeson, but would like to keep things uniform!
Try decode "\"JustSomeRandomString\"" :: Maybe Bar

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 do I write a ToJSON/FromJSON instance for a datatype with multiple constructors?

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"

Resources