Reputation: 2076
If I have a data type that looks like this:
data SumType = ABC | DEF deriving (Generic, ToJSON)
data MyType = MyType {field1 :: String, field2 :: SumType} deriving (Generic, ToJSON)
The above will generate a JSON that looks like: {"field1": "blah", "field2":"ABC"}
In practice, MyType
is a fairly complex type and I would like to keep the ToJSON
deriving but want to adjust just one field to use the show instance.
instance Show SumType where
show ABC = "abc-blah"
show DEF = "def-xyz"
Unfortunately, the above Show
instance does not get picked up by ToJSON
(I don't know if it is supposed to). Hand rolling ToJSON
for SumType
does not seem to work because it expects a key-value pair (Maybe there is another of way doing it?). In other words, the JSON will be like: {"field1": "blah", "field2":{"field3": "ABC"}}
-- I just want to change the way the value is stringified and not create a new object there.
Any suggestions on how I can change the output string of SumType
without manually creating ToJSON
for MyType
? So the output is {"field1": "blah", "field2":"abc-blah"}
Thanks!
Upvotes: 2
Views: 553
Reputation: 476709
I do not see what the problem is with defining a ToJSON
instance for the SumType
. You can do this with:
import Data.Aeson(ToJSON(toJSON), Value(String))
import Data.Text(pack)
instance ToJSON SumType where
toJSON = String . pack . show
Or if you want to use other strings for the ToJSON
than Show
:
{-# LANGUAGE OverloadedStrings #-}
import Data.Aeson(ToJSON(toJSON), Value(String))
instance ToJSON SumType where
toJSON ABC = String "ABC for JSON"
toJSON DEF = String "DEF for JSON"
Now Haskell will JSON-encode the SumType
as a JSON string:
Prelude Data.Aeson> encode ABC
"\"ABC for JSON\""
Prelude Data.Aeson> encode DEF
"\"DEF for JSON\""
You can do the same with FromJSON
to parse the JSON string back into a SumType
object:
{-# LANGUAGE OverloadedStrings #-}
import Data.Aeson(FromJSON(parseJSON), withText)
instance FromJSON SumType where
parseJSON = withText "SumType" f
where f "ABC for JSON" = return ABC
f "DEF for JSON" = return DEF
f _ = fail "Can not understand what you say!"
If we then parse back the JSON string, we get:
Prelude Data.Aeson> decode "\"ABC for JSON\"" :: Maybe SumType
Just ABC
Prelude Data.Aeson> decode "\"DEF for JSON\"" :: Maybe SumType
Just DEF
Prelude Data.Aeson> decode "\"other JSON string\"" :: Maybe SumType
Nothing
Prelude Data.Aeson> decode "{}" :: Maybe SumType
Nothing
So in case we do not decode a JSON string that follows one of the patterns we have defined, the parsing will fail. The same happens if we do not provide a JSON string, but for instance an empty JSON object.
Additional notes:
- Since
SumType
here has two values, you can also use a JSON boolean to encode the values.- you can also encode on different JSON objects. You can for instance use the JSON string for
ABC
, and an integer forDEF
. Although I would advice not to do this until there are good reasons (if for instanceABC
carries only a string, andDEF
ony an integer).- usually the more complex you make encoding, the more complex decoding will be.
Upvotes: 2