skip to Main Content

Let’s say I have the following JSON values.

{
  "fieldName1": 5,
  "value1": "Hello"
}

and

{
  "fieldName2": 7,
  "value1": "Welcome"
}

I have the following type in Haskell.

data Greeting 
  = Greeting
      {
        count :: Int,
        name :: Text
      }
  deriving (Generic, Show, Eq)

How do I parse this JSON into Haskell where fieldName1 or fieldName2 values should be parsed as count value?

I tried to solve this by doing something like shown below.

instance FromJSON Greeting where
  parseJSON = withObject "Greeting" $ obj -> do
    count1 <- obj .:? "fieldName1"
    count2 <- obj .:? "fieldName2"
    name <- obj .: "value1"
    count <-
      case (count1, count2) of
        (Just count, Nothing) -> return count
        (Nothing, Just count) -> return count
        _ -> fail $ Text.unpack "Field missing"
    return Greeting {count = count, name = name}

It works but is very cumbersome and if there are more than 2 alternative values, it becomes a lot more complex. Is there any way to solve this in a simpler way?

2

Answers


  1. A slightly better variant:

    import Control.Applicative ((<|>))
    
    instance FromJSON Greeting where
      parseJSON = withObject "Greeting" $ obj -> do
        count1 <- obj .:? "fieldName1"
        count2 <- obj .:? "fieldName2"
        name <- obj .: "value1"
        count <- maybe
           (fail $ Text.unpack "Field missing") -- fail case
           return                               -- success case
           (count1 <|> count2)                  -- take the first success
        return Greeting {count = count, name = name}
    

    Essentially, c1 <|> ... <|> cn is the first Just if there is one, and Nothing otherwise.

    You can also use case of instead of maybe, if you prefer. If this is frequently used, I’d put maybe (fail $ ...) return in a separate definition, so to give it a nice name.

    Login or Signup to reply.
  2. The Parser monad where parseJSON runs is itself an Alternative, so you can use the alternation (<|>) operator within the parser definition:

    instance FromJSON Greeting where
      parseJSON = withObject "Greeting" $ o -> do
        Greeting <$> (o .: "fieldName1" <|> o .: "fieldName2"
                      <|> fail "no count field")
                 <*> o .: "value1"
    

    If multiple "count" fields are present, this will take the first one that parses.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search