问题描述
具有以下类型和实例派生:
{-# LANGUAGE RecordWildCards #-}
import Data.Aeson
import Data.Text
data MyParams = MyParams {
mpFoo :: Maybe Text,mpBar :: Maybe Text
} deriving Show
instance FromJSON MyParams where
parseJSON = withObject "MyParams" $ \q -> do
mpFoo <- q .:? "foo"
mpBar <- q .:? "bar"
pure MyParams {..}
如何确保以下 JSON 会失败?
{
"foo": "this is a valid field name","baa": "this is an invalid field name"
}
通过上面的代码,这个 JSON 成功了,因为 1. bar
是可选的,所以 parseJSON 如果没有找到它不会抱怨,并且 2. baa
不会抛出任何错误但是反而会被忽略。 (1) 和 (2) 的组合意味着无法捕获字段名称中的拼写错误并且会被静默接受,尽管会生成错误的结果 (MyParams { foo = Just(this is a valid field name),bar = nothing }
)。
事实上,这个 JSON 字符串也应该失败:
{
"foo": "this is fine","bar": "this is fine","xyz": "should trigger failure but doesn't with the above code"
}
TL;DR:当 JSON 包含任何与 parseJSON
或 foo
不匹配的字段名称时,如何使 bar
失败?
解决方法
不要忘记您在 q
中可以访问的 withObject
只是一个 HashMap
。所以,你可以写:
import qualified Data.HashMap.Strict as HM
import qualified Data.HashSet as HS
import Control.Monad (guard)
instance FromJSON MyParams where
parseJSON = withObject "MyParams" $ \q -> do
mpFoo <- q .:? "foo"
mpBar <- q .:? "bar"
guard $ HM.keysSet q `HS.isSubsetOf` HS.fromList ["foo","bar"]
pure MyParams {..}
这将保证json只有至多元素"foo"
和"bar"
。
但是,考虑到 aeson
免费为您提供所有这些,这确实有点过分。如果您可以导出 Generic
,那么您只需调用 genericParseJSON
,如下所示:
{-# LANGUAGE DeriveGeneric #-}
data MyParams = MyParams {
mpFoo :: Maybe Text,mpBar :: Maybe Text
} deriving (Show,Generic)
instance FromJSON MyParams where
parseJSON = genericParseJSON $ defaultOptions
{ rejectUnknownFields = True,fieldLabelModifier = map toLower . drop 2
}
这里我们通过两种方式调整默认解析选项:首先,我们告诉它拒绝未知字段,这正是您所要求的,其次,我们告诉它如何从字段名称 "foo"
(对于 "mpFoo"
也是如此)。