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

How to correctly error out in JSON parsing with Data.Aeson

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

nonEmptyList 变成 Maybe NonEmpty,我正在尝试正确处理 List 确实为空的情况,我必须中止解析。这个解析实际上是在 parseJsonBody 内部完成的,这意味着我不想 error "foo" 我想摆脱它,但我想 return mzero (或其他任何东西会成功的,mzero 是我迄今为止偶然发现的唯一东西)这样处理程序就可以正确地 return 发送 400 而不是崩溃 500。

下面的方法可以编译,但据我所知,它几乎等于 error 或在 parseJSON 内部抛出某种其他形式的异常。但是,如果我 return 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

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

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

-- toNonEmptyP :: Parser [a] -> Parser NE.NonEmtpy
toNonEmptyP p = fmap NE.nonEmpty p >>= maybe mzero return

我们将 NE.nonEmpty 映射到我们的常规列表解析器,这给了我们 Parser (Maybe NE.NonEmpty)。然后,我们用 maybe 检查 Maybe,如果它是 Nothing,则使用 mzero,或者 return 将已解析的值返回到解析上下文。您的 FromJSON 实例然后归结为

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

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