ToJSON and FromJSON instances for nested Enum types - haskell

I am currently wrapping a Rest(ish) API. The JSON looks something like this, but more extensive:
{ 'a' : 'Bar1 Bar1B' }
{ 'a' : 'Bar2 Bar2A' }
This seems like it would be well represented by Enum types. For example:
data Foo = Foo { a :: Bar }
data Bar = Bar1 Bar1 | Bar2 Bar2
data Bar1 = Bar1A | Bar1B
data Bar2 = Bar2A | Bar2B
I'm having two problems.
While I can write ToJSON instances quite easily:
instance ToJSON Bar1 where
ToJSON Bar1A = String "Bar1A"
ToJSON Bar1B = String "Bar1B"
when I write the corresponding FromJSON instances, they fail to decode:
instance FromJSON Bar1 where
parseJSON (String "Bar1A") = return Bar1A
parseJSON (String "Bar1B") = return Bar1B
parseJSON _ = mzero
Why is this?
Secondly, this seems like it will involve me writing a huge amount of boilerplate. Is there any way around this? Using Show/Read, or template haskell, for example?

You can't decode a bare Bar1A value because a bare string is not valid JSON. Only arrays or objects are allowed at top-level.
Derived instances for simple examples like you're talking about here are quite straightforward using -XDeriveDataTypeable. See the Aeson docs for details.

Related

Generating Swagger for Haskell union type

I have a data structure of the following form:
data MyType =
Foo Int String
| Bar Int Int
| Baz String
I've manually generated an aeson ToJSON instance:
instance ToJSON MyType where
toJSON (Foo i s) = object [
"tag" .= ("foo" :: Text),
"intField" .= i,
"stringField" = s]
-- and so on.
Now I want a Swagger schema for it. Because I have a custom ToJSON instance I'm going to have to define a matching instance of ToSchema. I could just declare it as a list of mostly-optional field names and a mandatory tag string, but it really ought to list the possible tag values and associate them with different fields.
The OpenAPI specification talks about the discriminator object for doing this, and I've found the corresponding function in the Haskell swagger2 package. But I can't see how to get the mapping from discriminator value to sub-schema. Does anyone have any examples of how to do this?

how to handle capital case in JSON?

This is a stupid question, and I have tried to understand from different tutorials. When having a JSON with capital case Haskell crashes as explained by others (https://mail.haskell.org/pipermail/beginners/2013-October/012865.html). As suggested it could be solved with deriving from deriveFromJSON. DeriveJSON requires a function input, how should I write the derive statement in the below code? I am missing something in my understanding, and would appreciate any help.
import Data.Aeson.TH
data Person = Person {
Foo :: String
, bar :: String
} deriving (Eq, Show, deriveJSON)
main = do
let b = Person "t" "x"
print b
deriveJSON and friends are Template Haskell functions which will generate instances for you. As such, you should not try to list them in the deriving clause. Instead, call them in a splice at the top level like this:
{-# LANGUAGE TemplateHaskell #-}
import Data.Aeson.TH
data Person = Person {
foo :: String,
bar :: String
} deriving (Eq, Show)
$(deriveJSON defaultOptions ''Person)
As mentioned in the mailing list, you can customize the field names by overriding the fieldLabelModifier function of the defaultOptions record, for example this will change the JSON name of foo to Foo:
$(deriveFromJSON defaultOptions {
fieldLabelModifier = let f "foo" = "Foo"
f other = other
in f
} ''Person)
Do you have any control of the instance being generated? If so, don't emit keys starting with capital letters.
If not: Just define the instance yourself instead of deriving it. It's just a couple lines of code.

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

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"

What is a better way to decode/encode binary little endian data in Haskell?

What is a best approach to get rid of a boilerplate code that serializes/deserializes binary data in Haskell, taking endianness into account? I.e., given this struct:
data Foobar = Foobar { foo :: Word16, bar :: Word32 }
And derived Data.Binary.Binary type class instance:
instance Binary Foobar where
get = do
foo <- get
bar <- get
return $ Foobar foo bar
decode stream :: Foobar treats the data as big endian.
Obvious way is to use getWord16le/getWord32le functions, but it involves lots of manual work (which could be automatically and nicely done by Template Haskell coupled with derive).
Perhaps, parametrized types are the solution?
How about defining little-endian newtypes for words?
newtype LWord16 = LWord16 { unLWord16 :: Word16 }
newtype LWord32 = LWord32 { unLWord32 :: Word32 }
instance Binary LWord16 where get = LWord16 <$> getWord16le
instance Binary LWord32 where get = LWord32 <$> getWord32le
Then deriving Binary for the definition
data Foobar = Foobar { foo :: LWord16, bar :: LWord32 }
should do the right thing.
You can define a typeclass for different Word types, such as:
class BinaryEndian a where
getEndian :: Get a
putEndian :: a -> Put
instance BinaryEndian Word16 where
getEndian = getWord16le
putEndian = putWord16le
etc.
That would make TH code perhaps a little easier to write.

Resources