I'm trying to parse JSON to produce a type with multiple constructors. The challenge is that the type is encoded in the name of a key which contains the required data. In theory I could use a bunch of .:? calls and then check if given key returns Just but I think there must be a better way. I looked at asum but this didn't help me much (probably because of my unfamiliarity with it).
import Data.Aeson
import Data.Time.Clock
data Request = Req1 { id :: String, properties :: Value }
| Req2 { id :: String, properties :: Value }
| Req3 { id :: String, time :: UTCTime }
instance FromJSON Request where
parseJSON = withObject "message" $ \o ->
-- ???
Example requests:
{"req1": {"id": "345", "p1": "v1", "p2": "v2"}}
{"req2": {"id": "654", "p3", "v3"}}
{"req3": {"id": "876", "time": 1234567890}}
Here's how to manually inspect an Object:
{-# LANGUAGE OverloadedStrings #-}
import Data.Aeson
import Data.Time.Clock
import qualified Data.HashMap.Strict as H
import Control.Monad
type Val = Int
data Request = Req1 { id :: String, properties :: Val }
| Req2 { id :: String, properties :: Val }
| Req3 { id :: String, time :: UTCTime }
instance FromJSON Request where
parseJSON (Object v) =
case H.lookup "req1" v of
Just (Object h) -> Req1 <$> h .: "id" <*> h .: "properties"
Nothing ->
case H.lookup "req2" v of
Just (Object h) -> Req2 <$> h .: "id" <*> h .: "properies"
Nothing ->
case H.lookup "req3" v of
Just (Object h) -> Req3 <$> h .: "id" <*> h .: "time"
Nothing -> mzero
If the key req1 exists it will assume it is a Req1 value; else if the key req2 exists it will try to parse it as a Req2 value; etc. for req3. If none of those keys exist it will fail.
Instead of mzero you can also use fail "..." to display a custom error message.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With