Search code examples
haskellaeson

Serialization of a basic sum type in Json with Aeson


type GoalDescription = Text

data GoalStatus = Created | Accomplished | InProgress | GivenUp deriving (Show , Eq , Generic )

data Goal = Goal {workspaceId ::WorkspaceId , goalId :: GoalId , description :: GoalDescription , status :: GoalStatus} deriving (Show , Eq , Generic )

instance ToJSON Goal where
  toJSON (Goal {workspaceId, goalId ,description,status } ) = object [
            "workspaceId" .= workspaceId,
            "goalId" .= goalId,
            "description" .= description,
            "status" .= status]

instance FromJSON Goal  where

    parseJSON (Object jsonObject) = Goal <$> jsonObject .: "workspaceId" <*>  jsonObject .: "goalId" <*>  jsonObject .: "description" <*>  jsonObject .: "status"
    parseJSON _ =  error $ "Json format not expected"

I want to implement the FromJSON and ToJSON of GoalStatus that way: Goal {.. status:"accomplished"} or Goal {.. status:"inProgress"} etc... somehow I don't know how to implement these type classes without having a key -> value structure... GoalStatus should be only converted into a String Text without Keys attached to the value..

I have this temporary solution where I had to add an unnecessary key named "value" :

instance ToJSON GoalStatus where
    toJSON (Created) = object ["value" .= String "created"]
    toJSON (InProgress) = object ["value" .= String "inProgress"]
    toJSON (Accomplished) = object ["value" .= String "accomplished"]
    toJSON (GivenUp) = object ["value" .= String "GivenUp"]


instance FromJSON GoalStatus  where

  parseJSON (Object o) = do
     value <- o .: "value"
     case value of
          String status | (unpack status) == "created" -> return Created
          String status | (unpack status) == "inProgress" -> return InProgress
          String status | (unpack status) == "accomplished" -> return Accomplished
          String status | (unpack status) == "accomplished" -> return GivenUp
          _ -> error $ "Json format not expected"
  parseJSON _ =  error $ "Json format not expected"

Solution

  • String !Text is a constructor of Value and object has the type signature [Pair] -> Value where Pair is (Text, Value). You can use String to make the Value in ToJSON and then match on the particular shapes of the String when parsing in FromJSON.

    instance ToJSON GoalStatus where
      toJSON (Created) = String "created"
      toJSON (InProgress) = String "inProgress"
      toJSON (Accomplished) = String "accomplished"
      toJSON (GivenUp) = String "givenUp"
    
    instance FromJSON GoalStatus  where
      parseJSON (String s) = case unpack s of
        "created" -> return Created
        "inProgress" -> return InProgress
        "accomplished" -> return Accomplished
        "givenUp" -> return GivenUp
        _ -> error $ "Json format not expected"
      parseJSON _ =  error $ "Json format not expected"