snippetMinor
Embedding a Text -> Maybe MyDataType function in a Parser to parse JSON into a custom data type
Viewed 0 times
mydatatypeparserintotextfunctionjsonparsetypecustomembedding
Problem
I have these types:
I want to use Aeson to create PizzaOrder instances from this JSON structure:
I have this function to convert from a Text to a PizzaType:
(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
which I use like this:
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_specialInstruction
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 fail err
Just x -> pure xwhich I use like this:
instance FromJSON PizzaOrder where
parseJSON (Object v) = PizzaOrder
parseMaybe "Unknown pizza type" convertPizzaType (v .: "type")
v .: "specialInstructions"
parseJSON _ = mzeroMy 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_specialInstruction
Solution
Modern functional programming and pizza are best friends, so I commend the usage of Haskell here.
As
I also recommend using two helpers:
-
-
As
PizzaType is already its own type, it's probably easiest to implement a FromJSON PizzaType instance. We know Parser is a monad, so we can use >>= to sequence parser computations together.instance FromJSON PizzaType where
parseJSON (String s) =
maybe err pure (convertPizzaType s)
where
err = (fail . unpack) ("PizzaType: unknown type " <> s)
parseJSON invalid =
typeMismatch "PizzaType" invalid
instance FromJSON PizzaOrder where
parseJSON (Object v) =
PizzaOrder (v .: "type" >>= parseJSON) v .: "specialInstructions"
parseJSON invalid =
typeMismatch "PizzaOrder" invalidI also recommend using two helpers:
-
typeMismatch to make the Aeson errors a little better. It's easy, with large JSON input, to quickly run into error hell where you have no idea where an error is coming from. Best to be specific early on.-
maybe to convert values from Maybe a to Parser a.Code Snippets
instance FromJSON PizzaType where
parseJSON (String s) =
maybe err pure (convertPizzaType s)
where
err = (fail . unpack) ("PizzaType: unknown type " <> s)
parseJSON invalid =
typeMismatch "PizzaType" invalid
instance FromJSON PizzaOrder where
parseJSON (Object v) =
PizzaOrder <$> (v .: "type" >>= parseJSON) <*> v .: "specialInstructions"
parseJSON invalid =
typeMismatch "PizzaOrder" invalidContext
StackExchange Code Review Q#115190, answer score: 3
Revisions (0)
No revisions yet.