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?
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.
If you want to process field names more programmatically (for example, if you want to accept a single field whose name starts with the prefix "field"
while rejecting cases with multiple matching fields), then note that o
is a KeyMap
that can be processed with the functions in the Data.Aeson.KeyMap
and Data.Aeson.Key
modules:
import Data.Aeson
import qualified Data.Aeson.Key as Key
import qualified Data.Aeson.KeyMap as KeyMap
import Data.List (isPrefixOf)
instance FromJSON Greeting where
parseJSON = withObject "Greeting" $ \o -> do
-- get all values for "field*" keys
let fields = [v | (k, v) <- KeyMap.toList o
, "field" `isPrefixOf` Key.toString k]
case fields of
[v] -> Greeting <$> parseJSON v <*> o .: "value1"
[] -> fail "no \"field*\" fields"
_ -> fail "multiple \"field*\" fields"