on master help sign in
Add option to reject unknown fields (#207) (#749)

This commit adds rejectUnknownFields as a Option that affects parsing. Parsing will succeed only if all of the fields in the raw JSON value are present in the record to which it will be parsed. It supports both Generics and TH, though the TH implementation has less detailed messaging. This seems to be a pattern througout though, so it should be consistent with other parsing errors.

commit817ad9471653dfefcfe0c6d78bd609f0ce0586b5 authorrmanne <rahul_manne@hotmail.com> dateThu 6 Feb 2020, 09:00 (7 years ago) parent4945c67
10 files changed+240−26
unifiedsplit
Data/Aeson/TH.hs+24−4 Data/Aeson/Types.hs+1 Data/Aeson/Types/FromJSON.hs+51−16 Data/Aeson/Types/Internal.hs+9−1 tests/Encoders.hs+25 tests/ErrorMessages.hs+26 tests/Instances.hs+1−1 tests/Options.hs+11 tests/UnitTests.hs+84−4 tests/golden/generic.expected+8
Data/Aeson/TH.hs+24−4
@@ -143,7 +143,7 @@ import Language.Haskell.TH.Syntax (mkNameG_tc)
143143import Text.Printf (printf)
144144import qualified Data.Aeson.Encoding.Internal as E
145145import qualified Data.Foldable as F (all)
146−import qualified Data.HashMap.Strict as H (lookup, toList)
146+import qualified Data.HashMap.Strict as H (difference, fromList, keys, lookup, toList)
147147import qualified Data.List.NonEmpty as NE (length, reverse)
148148import qualified Data.Map as M (fromList, keys, lookup , singleton, size)
149149import qualified Data.Semigroup as Semigroup (Option(..))
@@ -922,12 +922,32 @@ parseRecord :: JSONClass
922922 -> Name
923923 -> [Name]
924924 -> Name
925+ -> Bool
925926 -> ExpQ
926−parseRecord jc tvMap argTys opts tName conName fields obj =
927+parseRecord jc tvMap argTys opts tName conName fields obj inTaggedObject =
928+ (if rejectUnknownFields opts
929+ then infixApp checkUnknownRecords [|(>>)|]
930+ else id) $
927931 foldl' (\a b -> infixApp a [|(<*>)|] b)
928932 (infixApp (conE conName) [|(<$>)|] x)
929933 xs
930934 where
935+ tagFieldNameAppender =
936+ if inTaggedObject then (tagFieldName (sumEncoding opts) :) else id
937+ knownFields = appE [|H.fromList|] $ listE $
938+ map (\knownName -> tupE [appE [|T.pack|] $ litE $ stringL knownName, [|()|]]) $
939+ tagFieldNameAppender $ map nameBase fields
940+ checkUnknownRecords =
941+ caseE (appE [|H.keys|] $ infixApp (varE obj) [|H.difference|] knownFields)
942+ [ match (listP []) (normalB [|return ()|]) []
943+ , newName "unknownFields" >>=
944+ \unknownFields -> match (varP unknownFields)
945+ (normalB $ appE [|fail|] $ infixApp
946+ (litE (stringL "Unknown fields: "))
947+ [|(++)|]
948+ (appE [|show|] (varE unknownFields)))
949+ []
950+ ]
931951 x:xs = [ [|lookupField|]
932952 `appE` dispatchParseJSON jc conName tvMap argTy
933953 `appE` litE (stringL $ show tName)
@@ -1002,7 +1022,7 @@ parseArgs jc tvMap tName opts
10021022 , constructorFields = argTys }
10031023 (Left (_, obj)) = do
10041024 argTys' <- mapM resolveTypeSynonyms argTys
1005− parseRecord jc tvMap argTys' opts tName conName fields obj
1025+ parseRecord jc tvMap argTys' opts tName conName fields obj True
10061026parseArgs jc tvMap tName opts
10071027 info@ConstructorInfo { constructorName = conName
10081028 , constructorVariant = RecordConstructor fields
@@ -1017,7 +1037,7 @@ parseArgs jc tvMap tName opts
10171037 argTys' <- mapM resolveTypeSynonyms argTys
10181038 caseE (varE valName)
10191039 [ match (conP 'Object [varP obj]) (normalB $
1020− parseRecord jc tvMap argTys' opts tName conName fields obj) []
1040+ parseRecord jc tvMap argTys' opts tName conName fields obj False) []
10211041 , matchFailed tName conName "Object"
10221042 ]
10231043
Data/Aeson/Types.hs+1
@@ -124,6 +124,7 @@ module Data.Aeson.Types
124124 , sumEncoding
125125 , unwrapUnaryRecords
126126 , tagSingleConstructors
127+ , rejectUnknownFields
127128
128129 -- ** Options utilities
129130 , SumEncoding(..)
Data/Aeson/Types/FromJSON.hs+51−16
@@ -11,6 +11,7 @@
1111{-# LANGUAGE RecordWildCards #-}
1212{-# LANGUAGE ScopedTypeVariables #-}
1313{-# LANGUAGE TypeOperators #-}
14+{-# LANGUAGE TupleSections #-}
1415{-# LANGUAGE UndecidableInstances #-}
1516{-# LANGUAGE ViewPatterns #-}
1617
@@ -1200,9 +1201,9 @@ class FromTaggedObject' arity f isRecord where
12001201 :: String :* ConName :* TypeName :* Options :* FromArgs arity a
12011202 -> Object -> Tagged isRecord (Parser (f a))
12021203
1203−instance (RecordFromJSON arity f) => FromTaggedObject' arity f True where
1204+instance (RecordFromJSON arity f, FieldNames f) => FromTaggedObject' arity f True where
12041205 -- Records are unpacked in the tagged object
1205− parseFromTaggedObject' (_ :* p) = Tagged . recordParseJSON p
1206+ parseFromTaggedObject' (_ :* p) = Tagged . recordParseJSON (True :* p)
12061207
12071208instance (ConsFromJSON arity f) => FromTaggedObject' arity f False where
12081209 -- Nonnullary nonrecords are encoded in the contents field
@@ -1243,11 +1244,11 @@ instance OVERLAPPING_
12431244 ) => ConsFromJSON' arity (S1 s a) True where
12441245 consParseJSON' p@(cname :* tname :* opts :* fargs)
12451246 | unwrapUnaryRecords opts = Tagged . fmap M1 . gParseJSON opts fargs
1246− | otherwise = Tagged . withObject (showCons cname tname) (recordParseJSON p)
1247+ | otherwise = Tagged . withObject (showCons cname tname) (recordParseJSON (False :* p))
12471248
12481249instance RecordFromJSON arity f => ConsFromJSON' arity f True where
12491250 consParseJSON' p@(cname :* tname :* _) =
1250− Tagged . withObject (showCons cname tname) (recordParseJSON p)
1251+ Tagged . withObject (showCons cname tname) (recordParseJSON (False :* p))
12511252
12521253instance OVERLAPPING_
12531254 ConsFromJSON' arity U1 False where
@@ -1273,21 +1274,55 @@ instance (ProductFromJSON arity f, ProductSize f
12731274
12741275--------------------------------------------------------------------------------
12751276
1277+class FieldNames f where
1278+ fieldNames :: f a -> [Text] -> [Text]
1279+
1280+instance (FieldNames a, FieldNames b) => FieldNames (a :*: b) where
1281+ fieldNames _ =
1282+ fieldNames (undefined :: a x) .
1283+ fieldNames (undefined :: b y)
1284+
1285+instance (Selector s) => FieldNames (S1 s f) where
1286+ fieldNames _ = (pack (selName (undefined :: M1 _i s _f _p)) :)
1287+
12761288class RecordFromJSON arity f where
12771289 recordParseJSON
1290+ :: Bool :* ConName :* TypeName :* Options :* FromArgs arity a
1291+ -> Object -> Parser (f a)
1292+
1293+instance ( FieldNames f
1294+ , RecordFromJSON' arity f
1295+ ) => RecordFromJSON arity f where
1296+ recordParseJSON (fromTaggedSum :* p@(cname :* tname :* opts :* _)) =
1297+ \obj -> checkUnknown obj >> recordParseJSON' p obj
1298+ where
1299+ knownFields :: H.HashMap Text ()
1300+ knownFields = H.fromList $ map (,()) $
1301+ fieldNames (undefined :: f a)
1302+ [pack (tagFieldName (sumEncoding opts)) | fromTaggedSum]
1303+ checkUnknown =
1304+ if not (rejectUnknownFields opts)
1305+ then \_ -> return ()
1306+ else \obj -> case H.keys (H.difference obj knownFields) of
1307+ [] -> return ()
1308+ unknownFields -> contextCons cname tname $
1309+ fail ("unknown fields: " ++ show unknownFields)
1310+
1311+class RecordFromJSON' arity f where
1312+ recordParseJSON'
12781313 :: ConName :* TypeName :* Options :* FromArgs arity a
12791314 -> Object -> Parser (f a)
12801315
1281−instance ( RecordFromJSON arity a
1282− , RecordFromJSON arity b
1283− ) => RecordFromJSON arity (a :*: b) where
1284− recordParseJSON p obj =
1285− (:*:) <$> recordParseJSON p obj
1286− <*> recordParseJSON p obj
1316+instance ( RecordFromJSON' arity a
1317+ , RecordFromJSON' arity b
1318+ ) => RecordFromJSON' arity (a :*: b) where
1319+ recordParseJSON' p obj =
1320+ (:*:) <$> recordParseJSON' p obj
1321+ <*> recordParseJSON' p obj
12871322
12881323instance OVERLAPPABLE_ (Selector s, GFromJSON arity a) =>
1289− RecordFromJSON arity (S1 s a) where
1290− recordParseJSON (cname :* tname :* opts :* fargs) obj = do
1324+ RecordFromJSON' arity (S1 s a) where
1325+ recordParseJSON' (cname :* tname :* opts :* fargs) obj = do
12911326 fv <- contextCons cname tname (obj .: label)
12921327 M1 <$> gParseJSON opts fargs fv <?> Key label
12931328 where
@@ -1295,16 +1330,16 @@ instance OVERLAPPABLE_ (Selector s, GFromJSON arity a) =>
12951330 sname = selName (undefined :: M1 _i s _f _p)
12961331
12971332instance INCOHERENT_ (Selector s, FromJSON a) =>
1298− RecordFromJSON arity (S1 s (K1 i (Maybe a))) where
1299− recordParseJSON (_ :* _ :* opts :* _) obj = M1 . K1 <$> obj .:? pack label
1333+ RecordFromJSON' arity (S1 s (K1 i (Maybe a))) where
1334+ recordParseJSON' (_ :* _ :* opts :* _) obj = M1 . K1 <$> obj .:? pack label
13001335 where
13011336 label = fieldLabelModifier opts sname
13021337 sname = selName (undefined :: M1 _i s _f _p)
13031338
13041339-- Parse an Option like a Maybe.
13051340instance INCOHERENT_ (Selector s, FromJSON a) =>
1306− RecordFromJSON arity (S1 s (K1 i (Semigroup.Option a))) where
1307− recordParseJSON p obj = wrap <$> recordParseJSON p obj
1341+ RecordFromJSON' arity (S1 s (K1 i (Semigroup.Option a))) where
1342+ recordParseJSON' p obj = wrap <$> recordParseJSON' p obj
13081343 where
13091344 wrap :: S1 s (K1 i (Maybe a)) p -> S1 s (K1 i (Semigroup.Option a)) p
13101345 wrap (M1 (K1 a)) = M1 (K1 (Semigroup.Option a))
Data/Aeson/Types/Internal.hs+9−1
@@ -64,6 +64,7 @@ module Data.Aeson.Types.Internal
6464 , sumEncoding
6565 , unwrapUnaryRecords
6666 , tagSingleConstructors
67+ , rejectUnknownFields
6768 )
6869
6970 , SumEncoding(..)
@@ -624,10 +625,14 @@ data Options = Options
624625 , tagSingleConstructors :: Bool
625626 -- ^ Encode types with a single constructor as sums,
626627 -- so that `allNullaryToStringTag` and `sumEncoding` apply.
628+ , rejectUnknownFields :: Bool
629+ -- ^ Applies only to 'Data.Aeson.FromJSON' instances. If a field appears in
630+ -- the parsed object map, but does not appear in the target object, parsing
631+ -- will fail, with an error message indicating which fields were unknown.
627632 }
628633
629634instance Show Options where
630− show (Options f c a o s u t) =
635+ show (Options f c a o s u t r) =
631636 "Options {"
632637 ++ intercalate ", "
633638 [ "fieldLabelModifier =~ " ++ show (f "exampleField")
@@ -637,6 +642,7 @@ instance Show Options where
637642 , "sumEncoding = " ++ show s
638643 , "unwrapUnaryRecords = " ++ show u
639644 , "tagSingleConstructors = " ++ show t
645+ , "rejectUnknownFields = " ++ show r
640646 ]
641647 ++ "}"
642648
@@ -718,6 +724,7 @@ data JSONKeyOptions = JSONKeyOptions
718724-- , 'sumEncoding' = 'defaultTaggedObject'
719725-- , 'unwrapUnaryRecords' = False
720726-- , 'tagSingleConstructors' = False
727+-- , 'rejectUnknownFields' = False
721728-- }
722729-- @
723730defaultOptions :: Options
@@ -729,6 +736,7 @@ defaultOptions = Options
729736 , sumEncoding = defaultTaggedObject
730737 , unwrapUnaryRecords = False
731738 , tagSingleConstructors = False
739+ , rejectUnknownFields = False
732740 }
733741
734742-- | Default 'TaggedObject' 'SumEncoding' options:
tests/Encoders.hs+25
@@ -243,6 +243,31 @@ gSomeTypeToEncodingOmitNothingFields :: SomeType Int -> Encoding
243243gSomeTypeToEncodingOmitNothingFields = genericToEncoding optsOmitNothingFields
244244
245245
246+thSomeTypeParseJSONRejectUnknownFields :: Value -> Parser (SomeType Int)
247+thSomeTypeParseJSONRejectUnknownFields = $(mkParseJSON optsRejectUnknownFields ''SomeType)
248+
249+gSomeTypeParseJSONRejectUnknownFields :: Value -> Parser (SomeType Int)
250+gSomeTypeParseJSONRejectUnknownFields = genericParseJSON optsRejectUnknownFields
251+
252+
253+--------------------------------------------------------------------------------
254+-- Foo decoders
255+--------------------------------------------------------------------------------
256+
257+thFooParseJSONRejectUnknownFields :: Value -> Parser Foo
258+thFooParseJSONRejectUnknownFields = $(mkParseJSON optsRejectUnknownFields ''Foo)
259+
260+gFooParseJSONRejectUnknownFields :: Value -> Parser Foo
261+gFooParseJSONRejectUnknownFields = genericParseJSON optsRejectUnknownFields
262+
263+
264+thFooParseJSONRejectUnknownFieldsTagged :: Value -> Parser Foo
265+thFooParseJSONRejectUnknownFieldsTagged = $(mkParseJSON optsRejectUnknownFieldsTagged ''Foo)
266+
267+gFooParseJSONRejectUnknownFieldsTagged :: Value -> Parser Foo
268+gFooParseJSONRejectUnknownFieldsTagged = genericParseJSON optsRejectUnknownFieldsTagged
269+
270+
246271--------------------------------------------------------------------------------
247272-- Option fields
248273--------------------------------------------------------------------------------
tests/ErrorMessages.hs+26
@@ -136,6 +136,29 @@ outputGeneric choice = concat
136136 , "[1,"
137137 ]
138138
139+ , testWithSomeType "SomeType (reject unknown fields)"
140+ (select
141+ thSomeTypeParseJSONRejectUnknownFields
142+ gSomeTypeParseJSONRejectUnknownFields)
143+ [ "{\"tag\": \"record\", \"testOne\": 1.0, \"testZero\": 1}"
144+ , "{\"testZero\": 1}"
145+ , "{\"tag\": \"record\", \"testone\": true, \"testtwo\": null, \"testthree\": null}"
146+ ]
147+
148+ , testWithFoo "Foo (reject unknown fields)"
149+ (select
150+ thFooParseJSONRejectUnknownFields
151+ gFooParseJSONRejectUnknownFields)
152+ [ "{\"tag\": \"foo\"}"
153+ ]
154+
155+ , testWithFoo "Foo (reject unknown fields, tagged single)"
156+ (select
157+ thFooParseJSONRejectUnknownFieldsTagged
158+ gFooParseJSONRejectUnknownFieldsTagged)
159+ [ "{\"tag\": \"foo\", \"unknownField\": 0}"
160+ ]
161+
139162 , testWith "EitherTextInt"
140163 (select
141164 thEitherTextIntParseJSONUntaggedValue
@@ -196,3 +219,6 @@ testFor name _ = testWith name (parseJSON :: Value -> Parser a)
196219
197220testWithSomeType :: String -> (Value -> Parser (SomeType Int)) -> [L.ByteString] -> Output
198221testWithSomeType = testWith
222+
223+testWithFoo :: String -> (Value -> Parser Foo) -> [L.ByteString] -> Output
224+testWithFoo = testWith
tests/Instances.hs+1−1
@@ -29,7 +29,7 @@ import Data.Hashable.Time ()
2929-- "System" types.
3030
3131instance Arbitrary DotNetTime where
32− arbitrary = DotNetTime `liftM` arbitrary
32+ arbitrary = DotNetTime `fmap` arbitrary
3333 shrink = map DotNetTime . shrink . fromDotNetTime
3434
3535-- | Compare timezone part only on 'timeZoneMinutes'
tests/Options.hs+11
@@ -55,3 +55,14 @@ optsOptionField = optsDefault
5555 { fieldLabelModifier = const "field"
5656 , omitNothingFields = True
5757 }
58+
59+optsRejectUnknownFields :: Options
60+optsRejectUnknownFields = optsDefault
61+ { rejectUnknownFields = True
62+ }
63+
64+optsRejectUnknownFieldsTagged :: Options
65+optsRejectUnknownFieldsTagged = optsDefault
66+ { rejectUnknownFields = True
67+ , tagSingleConstructors = True
68+ }
tests/UnitTests.hs+84−4
@@ -33,15 +33,15 @@ import Data.Aeson.Parser
3333 ( json, jsonLast, jsonAccum, jsonNoDup
3434 , json', jsonLast', jsonAccum', jsonNoDup')
3535import Data.Aeson.Types
36− ( Options(..), Result(Success), ToJSON(..), Value(Array, Bool, Null, Object)
37− , camelTo, camelTo2, defaultOptions, formatPath, formatRelativePath
38− , omitNothingFields, parse)
36+ ( Options(..), Result(Success, Error), ToJSON(..)
37+ , Value(Array, Bool, Null, Number, Object, String), camelTo, camelTo2
38+ , defaultOptions, formatPath, formatRelativePath, omitNothingFields, parse)
3939import Data.Attoparsec.ByteString (Parser, parseOnly)
4040import Data.Char (toUpper)
4141import Data.Either.Compat (isLeft, isRight)
4242import Data.Hashable (hash)
4343import Data.HashMap.Strict (HashMap)
44−import Data.List (sort)
44+import Data.List (sort, isSuffixOf)
4545import Data.Maybe (fromMaybe)
4646import Data.Scientific (Scientific, scientific)
4747import Data.Tagged (Tagged(..))
@@ -112,6 +112,7 @@ tests = testGroup "unit" [
112112 , testGroup "SingleMaybeField" singleMaybeField
113113 , testCase "withEmbeddedJSON" withEmbeddedJSONTest
114114 , testCase "SingleFieldCon" singleFieldCon
115+ , testGroup "UnknownFields" unknownFields
115116 , testGroup "Ordering of object keys" keyOrdering
116117 , testCase "Ratio with denominator 0" ratioDenominator0
117118 , testCase "Rational parses number" rationalNumber
@@ -539,6 +540,7 @@ showOptions =
539540 ++ ", sumEncoding = TaggedObject {tagFieldName = \"tag\", contentsFieldName = \"contents\"}"
540541 ++ ", unwrapUnaryRecords = False"
541542 ++ ", tagSingleConstructors = False"
543+ ++ ", rejectUnknownFields = False"
542544 ++ "}")
543545 (show defaultOptions)
544546
@@ -583,6 +585,79 @@ singleFieldCon :: Assertion
583585singleFieldCon =
584586 assertEqual "fromJSON" (Right (SingleFieldCon 0)) (eitherDecode "0")
585587
588+newtype UnknownFields = UnknownFields { knownField :: Int }
589+ deriving (Eq, Show, Generic)
590+newtype UnknownFieldsTag = UnknownFieldsTag { tag :: Int }
591+ deriving (Eq, Show, Generic)
592+newtype UnknownFieldsUnaryTagged = UnknownFieldsUnaryTagged { knownFieldUnaryTagged :: Int }
593+ deriving (Eq, Show, Generic)
594+data UnknownFieldsSum
595+ = UnknownFields1 { knownField1 :: Int }
596+ | UnknownFields2 { knownField2 :: Int }
597+ deriving (Eq, Show, Generic)
598+
599+unknownFields :: [TestTree]
600+unknownFields = concat
601+ [ testsUnary
602+ "unary-unknown"
603+ (object [("knownField", Number 1), ("unknownField", Number 1)])
604+ (Error "nknown fields: [\"unknownField\"]" :: Result UnknownFields)
605+ , testsUnary
606+ "unary-unknown-tag"
607+ (object [("knownField", Number 1), ("tag", String "UnknownFields")])
608+ (Error "nknown fields: [\"tag\"]" :: Result UnknownFields)
609+ , testsUnaryTag
610+ "unary-explicit-tag"
611+ (object [("tag", Number 1)])
612+ (Success $ UnknownFieldsTag 1)
613+ , testsSum
614+ "sum-tag"
615+ (object [("knownField1", Number 1), ("tag", String "UnknownFields1")])
616+ (Success $ UnknownFields1 1)
617+ , testsSum
618+ "sum-unknown-in-branch"
619+ (object [("knownField1", Number 1), ("knownField2", Number 1), ("tag", String "UnknownFields1")])
620+ (Error "nknown fields: [\"knownField2\"]" :: Result UnknownFieldsSum)
621+ , testsSum
622+ "sum-unknown"
623+ (object [("knownField1", Number 1), ("unknownField", Number 1), ("tag", String "UnknownFields1")])
624+ (Error "nknown fields: [\"unknownField\"]" :: Result UnknownFieldsSum)
625+ , testsTagged
626+ "unary-tagged"
627+ (object [("knownFieldUnaryTagged", Number 1), ("tag", String "UnknownFieldsUnaryTagged")])
628+ (Success $ UnknownFieldsUnaryTagged 1)
629+ , -- Just a case to verify that the tag isn't optional, this is likely already tested by other unit tests
630+ testsTagged
631+ "unary-tagged-notag"
632+ (object [("knownFieldUnaryTagged", Number 1)])
633+ (Error "key \"tag\" not found" :: Result UnknownFieldsUnaryTagged)
634+ , testsTagged
635+ "unary-tagged-unknown"
636+ (object [ ("knownFieldUnaryTagged", Number 1), ("unknownField", Number 1)
637+ , ("tag", String "UnknownFieldsUnaryTagged")])
638+ (Error "nknown fields: [\"unknownField\"]" :: Result UnknownFieldsUnaryTagged)
639+ ]
640+ where
641+ opts = defaultOptions{rejectUnknownFields=True}
642+ taggedOpts = opts{tagSingleConstructors=True}
643+ assertApprox :: (Show a, Eq a) => Result a -> Result a -> IO ()
644+ assertApprox (Error expected) (Error actual) | expected `isSuffixOf` actual = return ()
645+ assertApprox expected actual = assertEqual "fromJSON" expected actual
646+ testsBase :: (Show a, Eq a) => (Value -> Result a) -> (Value -> Result a)
647+ -> String -> Value -> Result a -> [TestTree]
648+ testsBase th g name value expected =
649+ [ testCase (name ++ "-th") $ assertApprox expected (th value)
650+ , testCase (name ++ "-generic") $ assertApprox expected (g value)
651+ ]
652+ testsUnary :: String -> Value -> Result UnknownFields -> [TestTree]
653+ testsUnary = testsBase fromJSON (parse (genericParseJSON opts))
654+ testsUnaryTag :: String -> Value -> Result UnknownFieldsTag -> [TestTree]
655+ testsUnaryTag = testsBase fromJSON (parse (genericParseJSON opts))
656+ testsSum :: String -> Value -> Result UnknownFieldsSum -> [TestTree]
657+ testsSum = testsBase fromJSON (parse (genericParseJSON opts))
658+ testsTagged :: String -> Value -> Result UnknownFieldsUnaryTagged -> [TestTree]
659+ testsTagged = testsBase fromJSON (parse (genericParseJSON taggedOpts))
660+
586661testParser :: (Eq a, Show a)
587662 => String -> Parser a -> S.ByteString -> Either String a -> TestTree
588663testParser name json_ s expected =
@@ -678,3 +753,8 @@ deriveToJSON defaultOptions ''Foo
678753deriveToJSON1 defaultOptions ''Foo
679754
680755deriveJSON defaultOptions{omitNothingFields=True,unwrapUnaryRecords=True} ''SingleMaybeField
756+
757+deriveJSON defaultOptions{rejectUnknownFields=True} ''UnknownFields
758+deriveJSON defaultOptions{rejectUnknownFields=True} ''UnknownFieldsTag
759+deriveJSON defaultOptions{tagSingleConstructors=True,rejectUnknownFields=True} ''UnknownFieldsUnaryTagged
760+deriveJSON defaultOptions{rejectUnknownFields=True} ''UnknownFieldsSum
tests/golden/generic.expected+8
@@ -31,6 +31,14 @@ Error in $: parsing Types.SomeType failed, expected a 2-element Array, but encou
3131Error in $: parsing Types.SomeType failed, expected Array, but encountered Object
3232Error in $: not enough input. Expecting ',' or ']'
3333Error in $: not enough input. Expecting json list value
34+SomeType (reject unknown fields)
35+Error in $: parsing Types.SomeType(Record) failed, unknown fields: ["testZero"]
36+Error in $: parsing Types.SomeType failed, expected Object with key "tag" containing one of ["nullary","unary","product","record","list"], key "tag" not found
37+Error in $: parsing Types.SomeType(Record) failed, unknown fields: ["testtwo","testone","testthree"]
38+Foo (reject unknown fields)
39+Error in $: parsing Types.Foo(Foo) failed, unknown fields: ["tag"]
40+Foo (reject unknown fields, tagged single)
41+Error in $: parsing Types.Foo(Foo) failed, unknown fields: ["unknownField"]
3442EitherTextInt
3543Error in $: parsing Types.EitherTextInt(NoneNullary) failed, expected tag "nonenullary", but found tag "X"
3644Error in $: parsing Types.EitherTextInt(NoneNullary) failed, expected String, but encountered Array