Domanda

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.

È stato utile?

Soluzione

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 ""
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top