Question

I'm having some trouble figuring out how to define FromJSON instances for an Enum type that defines a choice between two other types. My hunch is that I don't have a full enough understanding of the , <*>, and (.:) operators, as well as how the Aeson Object type works, but I haven't been able to parse apart the compilers errors yet. (Thankfully, the ToJSON instance is simple enough.)

Given two child data types, I can define instances like this:

data ChoiceSelection =
   ChoiceSelection
      { csValue :: Type1  -- Type2 here also works fine
      } deriving (Show,Typeable)

data Type1 =
   Type1
      { t1Value :: Int
      } deriving (Show,Typeable)

data Type2 =
   Type2
      { t2Value :: Bool
      } deriving (Show,Typeable)

instance FromJSON ChoiceSelection where
   parseJSON (Object x) =   ChoiceSelection
                        <$> (x .: "csValue")
   parseJSON _          = mzero

instance FromJSON Type1 where
   parseJSON (Object x) =   Type1
                        <$> (x .: "t1Value")
   parseJSON _          = mzero

instance FromJSON Type2 where
   parseJSON (Object x) =   Type2
                        <$> (x .: "t2Value")
   parseJSON _          = mzero

instance ToJSON ChoiceSelection where
   toJSON (ChoiceSelection value) =
      object [ "csValue" .= value
             ]

instance ToJSON Type1 where
   toJSON (Type1 value) =
      object [ "t1Value" .= value
             ]

instance ToJSON Type2 where
   toJSON (Type2 value) =
      object [ "t2Value" .= value
             ]

This works fine, but I've been unable to define an instance for FromJSON for ExampleChoice as:

data ExampleChoice = Choice1 Type1
                   | Choice2 Type2
                   deriving (Show,Typeable)

data ChoiceSelection =
   ChoiceSelection
      { csValue :: ExampleChoice
      } deriving (Show,Typeable)

instance FromJSON ExampleChoice where
   parseJSON (Object x) = -- ???
   parseJSON _          = mzero

instance ToJSON ExampleChoice where
   toJSON (Choice1 t@(Type1 _)) = toJSON t
   toJSON (Choice2 t@(Type2 _)) = toJSON t

I've thought to try defining this as an msum, like so:

instance FromJSON ExampleChoice where
   parseJSON (Object x) =
      msum [ -- Some attempt at parsing Type1
           , -- Some attempt at parsing Type2
           , mzero
           ]
   parseJSON _          = mzero

But, I haven't been able to figure out that parsing yet.

I haven't yet tried using TemplateHaskell and deriveJSON to define this for me, but even if that doesn't cause problems, I'm curious about how to solve this problem.

Edit: deriveJSON works great. I'm still curious how to build this by hand, though.

Was it helpful?

Solution

You need to change your ToJSON instance such that you can identify which data constructor to be used (I haven't tested the code but I hope this gives you the idea):

import qualified Data.HashMap.Strict as H

instance ToJSON ExampleChoice where
   toJSON (Choice1 t@(Type1 _)) = object ["Choice1" .= t]
   toJSON (Choice2 t@(Type2 _)) = object ["Choice2" .= t]


instance FromJSON ExampleChoice
    parseJSON (Object (H.toList -> [(key, value)]))
        | key == "Choice1"  = Choice1 <$> parseJSON value
        | key == "Choice2" = Choice2 <$> parseJSON value
    parseJSON _        = fail ""
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top