防止 Aeson parseJSON 中的未知字段名称
Prevent unknown field names in Aeson parseJSON
具有以下类型和实例派生:
{-# 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 包含与 foo
或 [=13 不匹配的任何字段名称时,我如何使 parseJSON
失败=]?
不要忘记您在 withObject
中可以访问的 q
只是一个 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"
(对于 bar
也是如此)。
具有以下类型和实例派生:
{-# 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 包含与 foo
或 [=13 不匹配的任何字段名称时,我如何使 parseJSON
失败=]?
不要忘记您在 withObject
中可以访问的 q
只是一个 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"
(对于 bar
也是如此)。