I have these types:
data PizzaType = Cheese | Pepperoni | Anchovy
data PizzaOrder = PizzaOrder
{ p_type :: PizzaType
, p_specialInstructions :: Text
}
I want to use Aeson to create PizzaOrder instances from this JSON structure:
{
"type": "pepperoni",
"specialInstructions": "please cut into nine slices"
}
I have this function to convert from a Text to a PizzaType:
convertPizzaType :: Text -> Maybe PizzaType
convertPizzaType "cheese" = Just Cheese
convertPizzaType "pepperoni" = Just Pepperoni
convertPizzaType "anchovy" = Just Anchovy
convertPizzaType _ = Nothing
(The actual code to convert from a Text to my custom data type is much more involved, but it’s not the main point of my question.) In order to use my convertPizzaType function in the JSON parsing process, I wrote this general function
parseMaybe :: String -> (a -> Maybe b) -> Parser a -> Parser b
parseMaybe err f p = do
result <- p
case f result of
Nothing -> fail err
Just x -> pure x
which I use like this:
instance FromJSON PizzaOrder where
parseJSON (Object v) = PizzaOrder <$>
parseMaybe "Unknown pizza type" convertPizzaType (v .: "type") <*>
v .: "specialInstructions"
parseJSON _ = mzero
My question involves my parseMaybe function. It takes the raw input from Aeson, runs the given conversion function, and aborts the parsing if Nothing was returned. This seems to work but I have the sneaking suspicion that I’m reinventing a wheel. Is this a reasonable way to embed my convertPizzaType function in a Parser or should I be doing something simpler?
Complete runnable example
#!/usr/bin/env stack
-- stack --resolver lts-3.14 --install-ghc runghc --package aeson
{-# LANGUAGE OverloadedStrings #-}
import Control.Monad (mzero)
import Data.Aeson
import Data.Aeson.Types (Parser)
import Data.Text (Text)
data PizzaType = Cheese | Pepperoni | Anchovy deriving (Show)
data PizzaOrder = PizzaOrder
{ p_type :: PizzaType
, p_specialInstructions :: Text
}
instance FromJSON PizzaOrder where
parseJSON (Object v) = PizzaOrder <$>
parseMaybe "Unknown pizza type" convertPizzaType (v .: "type") <*>
v .: "specialInstructions"
parseJSON _ = mzero
parseMaybe :: String -> (a -> Maybe b) -> Parser a -> Parser b
parseMaybe err f p = do
result <- p
case f result of
Nothing -> fail err
Just x -> pure x
convertPizzaType :: Text -> Maybe PizzaType
convertPizzaType "cheese" = Just Cheese
convertPizzaType "pepperoni" = Just Pepperoni
convertPizzaType "anchovy" = Just Anchovy
convertPizzaType _ = Nothing
main :: IO ()
main = do
let example1 = "{\"type\": \"pepperoni\", \"specialInstructions\": \"none\"}"
parsedExample1 = decode example1 :: Maybe PizzaOrder
case parsedExample1 of
Nothing -> putStrLn "Example 1 couldn't be parsed"
Just o -> putStrLn $ "The type of example 1 was " ++ (show $ p_type o)
let example2 = "{\"type\": \"broccoli\", \"specialInstructions\": \"EXTRA BROCCOLI\"}"
parsedExample2 = decode example2 :: Maybe PizzaOrder
case parsedExample2 of
Nothing -> putStrLn "Example 2 couldn't be parsed"
Just o -> putStrLn $ "The type of example 2 was " ++ (show $ p_type o)