My type and correponding FromJSON implementation as listed below.
The nonEmpty
turns a List
into a Maybe NonEmpty
and I'm trying to correctly deal with the case where the List
is indeed empty and I have to abort the parsing. This parsing is actually done inside of parseJsonBody
, which means I don't want to error "foo"
my way out of it, but I want to return mzero
(or whatever else will do the trick, mzero
is the only thing I've stumbled upon so far) so that the handler correctly returns a 400 instead of crashing with a 500.
The approach below compiles, but as far as I can tell is pretty much equals to error
or some other form of exception throwing inside of the parseJSON. If I return mzero
however (e.g. with <*> mzero
instead of that line), it fails nicely as intended.
import qualified Data.List.NonEmpty as NE
data GSAnswer = GSAnswer { gsAnswerQuestionId :: Int
, gsAnswerResponses :: NE.NonEmpty GSResponse
} deriving (Show, Eq)
instance FromJSON GSAnswer where
parseJSON (Object o) =
GSAnswer <$> o .: "question-id"
-- how do I return mzero here based on NE.nonEmpty?
-- this will throw an exception right now on an empty list
<*> fmap (fromMaybe (fail "foo") . NE.nonEmpty) (o .: "responses")
parseJSON _ = mzero
One option would be to somehow pattern match on the result of fmap NE.nonEmpty (o .: "responses")
, but I can't quite figure out what the pattern would be there: doesn't look like Parser has any constructors?
Essentially, you need a Parser [a] -> Parser NE.NonEmpty
transformer, which is relatively easy:
-- toNonEmptyP :: Parser [a] -> Parser NE.NonEmtpy
toNonEmptyP p = fmap NE.nonEmpty p >>= maybe mzero return
We map NE.nonEmpty
on our regular list parser, which gives us Parser (Maybe NE.NonEmpty)
. We then inspect the Maybe
with maybe
and either use mzero
if it was Nothing
, or return
the parsed value back to the parsing context. Your FromJSON
instance then boils down to
instance FromJSON GSAnswer where
parseJSON (Object o) =
GSAnswer <$> o .: "question-id"
<*> toNonEmptyP (o .: "responses")
parseJSON _ = mzero
You can use fail msg
instead of mzero
to provide a custom error message, as fail :: String -> Parser a
doesn't bottom out.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With