如何在使用 Data.Aeson 解析 JSON 时正确出错

Ale*_*lin 6 monads parsing haskell applicative aeson

我的类型和相应的 FromJSON 实现如下所示。

nonEmpty把一个List成一个Maybe NonEmpty,我试图与其中的情况下正确处理List确实是空的,我不得不中止解析。这种解析实际上是在 内部完成的parseJsonBody,这意味着我不想error "foo"摆脱它,但我想返回mzero(或者其他任何可以解决问题的方法,这mzero是我迄今为止唯一偶然发现的),以便处理程序正确返回 400 而不是崩溃 500。

下面的方法可以编译,但据我所知,它几乎等于error或在 parseJSON 内部抛出某种其他形式的异常。mzero但是,如果我返回(例如使用<*> mzero而不是该行),它会按预期很好地失败。

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
Run Code Online (Sandbox Code Playgroud)

一种选择是以某种方式对 的结果进行模式匹配fmap NE.nonEmpty (o .: "responses"),但我无法弄清楚那里的模式是什么:看起来 Parser 没有任何构造函数?

Zet*_*eta 5

本质上,你需要一个Parser [a] -> Parser NE.NonEmpty变压器,这相对简单:

-- toNonEmptyP :: Parser [a] -> Parser NE.NonEmtpy
toNonEmptyP p = fmap NE.nonEmpty p >>= maybe mzero return
Run Code Online (Sandbox Code Playgroud)

我们映射NE.nonEmpty到常规列表解析器上,它为我们提供了Parser (Maybe NE.NonEmpty). 然后我们检查Maybewithmaybe并使用mzeroif it was Nothing,或者return将解析后的值返回到解析上下文。然后你的FromJSON实例归结为

instance FromJSON GSAnswer where
  parseJSON (Object o) =
    GSAnswer <$> o .: "question-id"
             <*> toNonEmptyP (o .: "responses")
  parseJSON _ = mzero
Run Code Online (Sandbox Code Playgroud)

您可以使用fail msg而不是mzero提供自定义错误消息,因为fail :: String -> Parser a不会触底。