diff --git a/aeson.cabal b/aeson.cabal index 09349501e..639ebd21b 100644 --- a/aeson.cabal +++ b/aeson.cabal @@ -165,6 +165,7 @@ test-suite aeson-tests PropUtils Regression.Issue351 Regression.Issue571 + Regression.Issue687 Regression.Issue967 SerializationFormatSpec Types @@ -174,6 +175,12 @@ test-suite aeson-tests UnitTests.KeyMapInsertWith UnitTests.MonadFix UnitTests.NullaryConstructors + UnitTests.OmitNothingFieldsNote + UnitTests.OptionalFields + UnitTests.OptionalFields.Common + UnitTests.OptionalFields.Generics + UnitTests.OptionalFields.Manual + UnitTests.OptionalFields.TH UnitTests.UTCTime build-depends: diff --git a/changelog.md b/changelog.md index 0b54378b0..e80e956df 100644 --- a/changelog.md +++ b/changelog.md @@ -1,12 +1,36 @@ For the latest version of this document, please see [https://github.com/haskell/aeson/blob/master/changelog.md](https://github.com/haskell/aeson/blob/master/changelog.md). -### 2.2 - -* Use `Data.Aeson.Decoding` parsing functions as default in `Data.Aeson`. -* Move `Data.Aeson.Parser` module into separate `attoparsec-aeson` package, as these parsers are not used by `aeson` itself anymore. -* Remove `cffi` flag. Then the C implementation for string unescaping was used for `text <2` versions. - The new native Haskell implementation introduced in version 2.0.3.0 is at least as fast. -* Drop instances for `attoparsec.Number`. +### 2.2.0.0 + +* Rework how `omitNothingFields` works. Add `allowOmittedFields` as a parsing counterpart. + + New type-class members were added: `omitField :: a -> Bool` to `ToJSON` and `omittedField :: Maybe a` to `FromJSON`. + These control which fields can be omitted. + The `.:?=`, `.:!=` and `.?=` operators were added to make use of these new members. + + GHC.Generics and Template Haskell deriving has been updated accordingly. + Note: They behave as the parsers have been written with `.:!=`, i.e. + if the field value is `null` it's passed to the underlying parser. + This doesn't make difference for `Maybe` or `Option`, but does make for + types which parser doesn't accept `null`. + (`()` parser accepts everything and `Proxy` accepts `null). + + In addition to `Maybe` (and `Option`) fields the `Data.Monoid.First` and `Data.Monoid.Last` are also omitted, + as well as the most newtype wrappers, when their wrap omittable type (e.g. newtypes in `Data.Monoid` and `Data.Semigroup`, `Identity`, `Const`, `Tagged`, `Compose`). + Additionall "boring" types like `()` and `Proxy` are omitted as well. + As the omitting is now uniform, type arguments are also omitted (also in `Generic1` derived instance). + + Resolves issues + [#687](https://github.com/haskell/aeson/issues/687), + [#571](https://github.com/haskell/aeson/issues/571), + [#792](https://github.com/haskell/aeson/issues/792). + +* Use `Data.Aeson.Decoding` parsing functions (introduced in version 2.1.2.0) as default in `Data.Aeson`. +* Move `Data.Aeson.Parser` module into separate [`attoparsec-aeson`](https://hackage.haskell.org/package/attoparsec-aeson) package, as these parsers are not used by `aeson` itself anymore. +* Use [`text-iso8601`](https://hackage.haskell.org/package/text-iso8601) package for parsing `time` types. These are slightly faster than previously used (copy of) `attoparsec-iso8601`. +* Remove `cffi` flag. Toggling the flag made `aeson` use a C implementation for string unescaping (used for `text <2` versions). + The new native Haskell implementation (introduced in version 2.0.3.0) is at least as fast. +* Drop instances for `Number` from `attoparsec` package. * Improve `Arbitrary Value` instance. ### 2.1.2.1 diff --git a/src/Data/Aeson.hs b/src/Data/Aeson.hs index adb2fd283..2c2eb83a2 100644 --- a/src/Data/Aeson.hs +++ b/src/Data/Aeson.hs @@ -76,6 +76,7 @@ module Data.Aeson , fromJSON , ToJSON(..) , KeyValue(..) + , KeyValueOmit(..) , () , JSONPath -- ** Keys for maps @@ -91,14 +92,18 @@ module Data.Aeson -- ** Liftings to unary and binary type constructors , FromJSON1(..) , parseJSON1 + , omittedField1 , FromJSON2(..) , parseJSON2 + , omittedField2 , ToJSON1(..) , toJSON1 , toEncoding1 + , omitField1 , ToJSON2(..) , toJSON2 , toEncoding2 + , omitField2 -- ** Generic JSON classes and options , GFromJSON , FromArgs @@ -123,6 +128,7 @@ module Data.Aeson , constructorTagModifier , allNullaryToStringTag , omitNothingFields + , allowOmittedFields , sumEncoding , unwrapUnaryRecords , tagSingleConstructors @@ -151,6 +157,8 @@ module Data.Aeson , (.:?) , (.:!) , (.!=) + , (.:?=) + , (.:!=) , object -- * Parsing , parseIndexedJSON diff --git a/src/Data/Aeson/Encoding/Internal.hs b/src/Data/Aeson/Encoding/Internal.hs index c04e8e79d..b8ce355a3 100644 --- a/src/Data/Aeson/Encoding/Internal.hs +++ b/src/Data/Aeson/Encoding/Internal.hs @@ -115,7 +115,7 @@ instance Ord (Encoding' a) where compare (Encoding a) (Encoding b) = compare (toLazyByteString a) (toLazyByteString b) --- | @since 2.2 +-- | @since 2.2.0.0 instance IsString (Encoding' a) where fromString = string diff --git a/src/Data/Aeson/TH.hs b/src/Data/Aeson/TH.hs index 430f709d8..088b1fbae 100644 --- a/src/Data/Aeson/TH.hs +++ b/src/Data/Aeson/TH.hs @@ -118,13 +118,12 @@ import Data.Char (ord) import Data.Aeson (Object, (.:), FromJSON(..), FromJSON1(..), FromJSON2(..), ToJSON(..), ToJSON1(..), ToJSON2(..)) import Data.Aeson.Types (Options(..), Parser, SumEncoding(..), Value(..), defaultOptions, defaultTaggedObject) import Data.Aeson.Types.Internal ((), JSONPathElement(Key)) -import Data.Aeson.Types.FromJSON (parseOptionalFieldWith) import Data.Aeson.Types.ToJSON (fromPairs, pair) import Data.Aeson.Key (Key) import qualified Data.Aeson.Key as Key import qualified Data.Aeson.KeyMap as KM import Data.Foldable (foldr') -import Data.List (genericLength, intercalate, partition, union) +import Data.List (genericLength, intercalate, union) import Data.List.NonEmpty ((<|), NonEmpty((:|))) import Data.Map (Map) import qualified Data.Monoid as Monoid @@ -135,9 +134,6 @@ import Text.Printf (printf) import qualified Data.Aeson.Encoding.Internal as E import qualified Data.List.NonEmpty as NE (length, reverse) import qualified Data.Map as M (fromList, keys, lookup , singleton, size) -#if !MIN_VERSION_base(4,16,0) -import qualified Data.Semigroup as Semigroup (Option(..)) -#endif import qualified Data.Set as Set (empty, insert, member) import qualified Data.Text as T (pack, unpack) import qualified Data.Vector as V (unsafeIndex, null, length, create, empty) @@ -324,10 +320,11 @@ consToValue _ _ _ _ [] = consToValue target jc opts instTys cons = autoletE liftSBS $ \letInsert -> do value <- newName "value" + os <- newNameList "_o" $ arityInt jc tjs <- newNameList "_tj" $ arityInt jc tjls <- newNameList "_tjl" $ arityInt jc - let zippedTJs = zip tjs tjls - interleavedTJs = interleave tjs tjls + let zippedTJs = zip3 os tjs tjls + interleavedTJs = flatten3 zippedTJs lastTyVars = map varTToName $ drop (length instTys - arityInt jc) instTys tvMap = M.fromList $ zip lastTyVars zippedTJs lamE (map varP $ interleavedTJs ++ [value]) $ @@ -456,40 +453,26 @@ argsToValue letInsert target jc tvMap opts multiCons (True,True,[_]) -> argsToValue letInsert target jc tvMap opts multiCons (info{constructorVariant = NormalConstructor}) _ -> do + argTys' <- mapM resolveTypeSynonyms argTys args <- newNameList "arg" $ length argTys' - let pairs | omitNothingFields opts = infixApp maybeFields - [|(Monoid.<>)|] - restFields - | otherwise = mconcatE (map pureToPair argCons) - - argCons = zip3 (map varE args) argTys' fields - - maybeFields = mconcatE (map maybeToPair maybes) - - restFields = mconcatE (map pureToPair rest) - - (maybes0, rest0) = partition isMaybe argCons -#if MIN_VERSION_base(4,16,0) - maybes = maybes0 - rest = rest0 -#else - (options, rest) = partition isOption rest0 - maybes = maybes0 ++ map optionToMaybe options -#endif - - maybeToPair = toPairLifted True - pureToPair = toPairLifted False - - toPairLifted lifted (arg, argTy, field) = - let toValue = dispatchToJSON target jc conName tvMap argTy - fieldName = fieldLabel opts field - e arg' = pairE letInsert target fieldName (toValue `appE` arg') - in if lifted - then do - x <- newName "x" - [|maybe mempty|] `appE` lam1E (varP x) (e (varE x)) `appE` arg - else e arg + let argCons = zip3 (map varE args) argTys' fields + + toPair (arg, argTy, fld) = + let fieldName = fieldLabel opts fld + toValue = dispatchToJSON target jc conName tvMap argTy + + omitFn :: Q Exp + omitFn + | omitNothingFields opts = dispatchOmitField jc conName tvMap argTy + | otherwise = [| const False |] + + in condE + (omitFn `appE` arg) + [| mempty |] + (pairE letInsert target fieldName (toValue `appE` arg)) + + pairs = mconcatE (map toPair argCons) match (conP conName $ map varP args) (normalB $ recordSumToValue letInsert target opts multiCons (null argTys) conName pairs) @@ -514,19 +497,6 @@ argsToValue letInsert target jc tvMap opts multiCons ) [] -isMaybe :: (a, Type, b) -> Bool -isMaybe (_, AppT (ConT t) _, _) = t == ''Maybe -isMaybe _ = False - -#if !MIN_VERSION_base(4,16,0) -isOption :: (a, Type, b) -> Bool -isOption (_, AppT (ConT t) _, _) = t == ''Semigroup.Option -isOption _ = False - -optionToMaybe :: (ExpQ, b, c) -> (ExpQ, b, c) -optionToMaybe (a, b, c) = ([|Semigroup.getOption|] `appE` a, b, c) -#endif - (<^>) :: ExpQ -> ExpQ -> ExpQ (<^>) a b = infixApp a [|(E.><)|] b infixr 6 <^> @@ -685,10 +655,11 @@ consFromJSON _ _ _ _ [] = consFromJSON jc tName opts instTys cons = do value <- newName "value" + os <- newNameList "_o" $ arityInt jc pjs <- newNameList "_pj" $ arityInt jc pjls <- newNameList "_pjl" $ arityInt jc - let zippedPJs = zip pjs pjls - interleavedPJs = interleave pjs pjls + let zippedPJs = zip3 os pjs pjls + interleavedPJs = flatten3 zippedPJs lastTyVars = map varTToName $ drop (length instTys - arityInt jc) instTys tvMap = M.fromList $ zip lastTyVars zippedPJs lamE (map varP $ interleavedPJs ++ [value]) $ lamExpr value tvMap @@ -953,6 +924,11 @@ parseRecord jc tvMap argTys opts tName conName fields obj inTaggedObject = (infixApp (conE conName) [|(<$>)|] x) xs where + lookupField :: Type -> Q Exp + lookupField argTy + | allowOmittedFields opts = [| lookupFieldOmit |] `appE` dispatchOmittedField jc conName tvMap argTy + | otherwise = [| lookupFieldNoOmit |] + tagFieldNameAppender = if inTaggedObject then (tagFieldName (sumEncoding opts) :) else id knownFields = appE [|KM.fromList|] $ listE $ @@ -969,7 +945,7 @@ parseRecord jc tvMap argTys opts tName conName fields obj inTaggedObject = (appE [|show|] (varE unknownFields))) [] ] - x:xs = [ [|lookupField|] + x:xs = [ lookupField argTy `appE` dispatchParseJSON jc conName tvMap argTy `appE` litE (stringL $ show tName) `appE` litE (stringL $ constructorTagModifier opts $ nameBase conName) @@ -1137,26 +1113,17 @@ parseTypeMismatch tName conName expected actual = , actual ] -class LookupField a where - lookupField :: (Value -> Parser a) -> String -> String - -> Object -> Key -> Parser a - -instance {-# OVERLAPPABLE #-} LookupField a where - lookupField = lookupFieldWith - -instance {-# INCOHERENT #-} LookupField (Maybe a) where - lookupField pj _ _ = parseOptionalFieldWith pj - -#if !MIN_VERSION_base(4,16,0) -instance {-# INCOHERENT #-} LookupField (Semigroup.Option a) where - lookupField pj tName rec obj key = - fmap Semigroup.Option - (lookupField (fmap Semigroup.getOption . pj) tName rec obj key) -#endif +lookupFieldOmit :: Maybe a -> (Value -> Parser a) -> String -> String -> Object -> Key -> Parser a +lookupFieldOmit maybeDefault pj tName rec obj key = + case KM.lookup key obj of + Nothing -> + case maybeDefault of + Nothing -> unknownFieldFail tName rec (Key.toString key) + Just x -> pure x + Just v -> pj v Key key -lookupFieldWith :: (Value -> Parser a) -> String -> String - -> Object -> Key -> Parser a -lookupFieldWith pj tName rec obj key = +lookupFieldNoOmit :: (Value -> Parser a) -> String -> String -> Object -> Key -> Parser a +lookupFieldNoOmit pj tName rec obj key = case KM.lookup key obj of Nothing -> unknownFieldFail tName rec (Key.toString key) Just v -> pj v Key key @@ -1287,20 +1254,26 @@ mkFunCommon consFun jc opts name = do !_ <- buildTypeInstance parentName jc ctxt instTys variant consFun jc parentName opts instTys cons +data FunArg = Omit | Single | Plural deriving (Eq) + dispatchFunByType :: JSONClass -> JSONFun -> Name -> TyVarMap - -> Bool -- True if we are using the function argument that works - -- on lists (e.g., [a] -> Value). False is we are using - -- the function argument that works on single values - -- (e.g., a -> Value). + -> FunArg -- Plural if we are using the function argument that works + -- on lists (e.g., [a] -> Value). Single is we are using + -- the function argument that works on single values + -- (e.g., a -> Value). Omit if we use it to check omission + -- (e.g. a -> Bool) -> Type -> Q Exp dispatchFunByType _ jf _ tvMap list (VarT tyName) = varE $ case M.lookup tyName tvMap of - Just (tfjExp, tfjlExp) -> if list then tfjlExp else tfjExp - Nothing -> jsonFunValOrListName list jf Arity0 + Just (tfjoExp, tfjExp, tfjlExp) -> case list of + Omit -> tfjoExp + Single -> tfjExp + Plural -> tfjlExp + Nothing -> jsonFunValOrListName list jf Arity0 dispatchFunByType jc jf conName tvMap list (SigT ty _) = dispatchFunByType jc jf conName tvMap list ty dispatchFunByType jc jf conName tvMap list (ForallT _ _ ty) = @@ -1319,24 +1292,29 @@ dispatchFunByType jc jf conName tvMap list ty = do tyVarNames :: [Name] tyVarNames = M.keys tvMap + args :: [Q Exp] + args + | list == Omit = map (dispatchFunByType jc jf conName tvMap Omit) rhsArgs + | otherwise = zipWith (dispatchFunByType jc jf conName tvMap) (cycle [Omit,Single,Plural]) (triple rhsArgs) + itf <- isInTypeFamilyApp tyVarNames tyCon tyArgs if any (`mentionsName` tyVarNames) lhsArgs || itf then outOfPlaceTyVarError jc conName else if any (`mentionsName` tyVarNames) rhsArgs - then appsE $ varE (jsonFunValOrListName list jf $ toEnum numLastArgs) - : zipWith (dispatchFunByType jc jf conName tvMap) - (cycle [False,True]) - (interleave rhsArgs rhsArgs) + then appsE $ varE (jsonFunValOrListName list jf $ toEnum numLastArgs) : args else varE $ jsonFunValOrListName list jf Arity0 -dispatchToJSON - :: ToJSONFun -> JSONClass -> Name -> TyVarMap -> Type -> Q Exp -dispatchToJSON target jc n tvMap = - dispatchFunByType jc (targetToJSONFun target) n tvMap False +dispatchToJSON :: ToJSONFun -> JSONClass -> Name -> TyVarMap -> Type -> Q Exp +dispatchToJSON target jc n tvMap = dispatchFunByType jc (targetToJSONFun target) n tvMap Single + +dispatchOmitField :: JSONClass -> Name -> TyVarMap -> Type -> Q Exp +dispatchOmitField jc n tvMap = dispatchFunByType jc ToJSON n tvMap Omit + +dispatchParseJSON :: JSONClass -> Name -> TyVarMap -> Type -> Q Exp +dispatchParseJSON jc n tvMap = dispatchFunByType jc ParseJSON n tvMap Single -dispatchParseJSON - :: JSONClass -> Name -> TyVarMap -> Type -> Q Exp -dispatchParseJSON jc n tvMap = dispatchFunByType jc ParseJSON n tvMap False +dispatchOmittedField :: JSONClass -> Name -> TyVarMap -> Type -> Q Exp +dispatchOmittedField jc n tvMap = dispatchFunByType jc ParseJSON n tvMap Omit -------------------------------------------------------------------------------- -- Utility functions @@ -1607,13 +1585,14 @@ Both. -- A mapping of type variable Names to their encoding/decoding function Names. -- For example, in a ToJSON2 declaration, a TyVarMap might look like -- --- { a ~> (tj1, tjl1) --- , b ~> (tj2, tjl2) } +-- { a ~> (o1, tj1, tjl1) +-- , b ~> (o2, tj2, tjl2) } -- --- where a and b are the last two type variables of the datatype, tj1 and tjl1 are --- the function arguments of types (a -> Value) and ([a] -> Value), and tj2 and tjl2 --- are the function arguments of types (b -> Value) and ([b] -> Value). -type TyVarMap = Map Name (Name, Name) +-- where a and b are the last two type variables of the datatype, +-- o1 and o2 are function argument of types (a -> Bool), +-- tj1 and tjl1 are the function arguments of types (a -> Value) +-- and ([a] -> Value), and tj2 and tjl2 are the function arguments of types (b -> Value) and ([b] -> Value). +type TyVarMap = Map Name (Name, Name, Name) -- | Returns True if a Type has kind *. hasKindStar :: Type -> Bool @@ -1658,9 +1637,11 @@ varTToNameMaybe _ = Nothing varTToName :: Type -> Name varTToName = fromMaybe (error "Not a type variable!") . varTToNameMaybe -interleave :: [a] -> [a] -> [a] -interleave (a1:a1s) (a2:a2s) = a1:a2:interleave a1s a2s -interleave _ _ = [] +flatten3 :: [(a,a,a)] -> [a] +flatten3 = foldr (\(a,b,c) xs -> a:b:c:xs) [] + +triple :: [a] -> [a] +triple = foldr (\x xs -> x:x:x:xs) [] -- | Fully applies a type constructor to its type variables. applyTyCon :: Name -> [Type] -> Type @@ -1951,6 +1932,17 @@ jsonClassName (JSONClass From Arity0) = ''FromJSON jsonClassName (JSONClass From Arity1) = ''FromJSON1 jsonClassName (JSONClass From Arity2) = ''FromJSON2 +jsonFunOmitName :: JSONFun -> Arity -> Name +jsonFunOmitName ToJSON Arity0 = 'omitField +jsonFunOmitName ToJSON Arity1 = 'liftOmitField +jsonFunOmitName ToJSON Arity2 = 'liftOmitField2 +jsonFunOmitName ToEncoding Arity0 = 'omitField +jsonFunOmitName ToEncoding Arity1 = 'liftOmitField +jsonFunOmitName ToEncoding Arity2 = 'liftOmitField2 +jsonFunOmitName ParseJSON Arity0 = 'omittedField +jsonFunOmitName ParseJSON Arity1 = 'liftOmittedField +jsonFunOmitName ParseJSON Arity2 = 'liftOmittedField2 + jsonFunValName :: JSONFun -> Arity -> Name jsonFunValName ToJSON Arity0 = 'toJSON jsonFunValName ToJSON Arity1 = 'liftToJSON @@ -1973,10 +1965,11 @@ jsonFunListName ParseJSON Arity0 = 'parseJSONList jsonFunListName ParseJSON Arity1 = 'liftParseJSONList jsonFunListName ParseJSON Arity2 = 'liftParseJSONList2 -jsonFunValOrListName :: Bool -- e.g., toJSONList if True, toJSON if False +jsonFunValOrListName :: FunArg -- e.g., toJSONList if True, toJSON if False -> JSONFun -> Arity -> Name -jsonFunValOrListName False = jsonFunValName -jsonFunValOrListName True = jsonFunListName +jsonFunValOrListName Omit = jsonFunOmitName +jsonFunValOrListName Single = jsonFunValName +jsonFunValOrListName Plural = jsonFunListName arityInt :: JSONClass -> Int arityInt = fromEnum . arity diff --git a/src/Data/Aeson/Types.hs b/src/Data/Aeson/Types.hs index f4b935b95..5b5566165 100644 --- a/src/Data/Aeson/Types.hs +++ b/src/Data/Aeson/Types.hs @@ -50,6 +50,7 @@ module Data.Aeson.Types -- ** Encoding , ToJSON(..) , KeyValue(..) + , KeyValueOmit(..) -- ** Keys for maps , ToJSONKey(..) @@ -72,14 +73,18 @@ module Data.Aeson.Types -- ** Liftings to unary and binary type constructors , FromJSON1(..) , parseJSON1 + , omittedField1 , FromJSON2(..) , parseJSON2 + , omittedField2 , ToJSON1(..) , toJSON1 , toEncoding1 + , omitField1 , ToJSON2(..) , toJSON2 , toEncoding2 + , omitField2 -- ** Generic JSON classes , GFromJSON @@ -111,13 +116,19 @@ module Data.Aeson.Types , (.:?) , (.:!) , (.!=) + , (.:?=) + , (.:!=) , object , parseField , parseFieldMaybe , parseFieldMaybe' + , parseFieldOmit + , parseFieldOmit' , explicitParseField , explicitParseFieldMaybe , explicitParseFieldMaybe' + , explicitParseFieldOmit + , explicitParseFieldOmit' , listEncoding , listValue @@ -132,6 +143,7 @@ module Data.Aeson.Types , constructorTagModifier , allNullaryToStringTag , omitNothingFields + , allowOmittedFields , sumEncoding , unwrapUnaryRecords , tagSingleConstructors diff --git a/src/Data/Aeson/Types/Class.hs b/src/Data/Aeson/Types/Class.hs index ecdb3fb7d..5aec453df 100644 --- a/src/Data/Aeson/Types/Class.hs +++ b/src/Data/Aeson/Types/Class.hs @@ -26,14 +26,18 @@ module Data.Aeson.Types.Class -- * Liftings to unary and binary type constructors , FromJSON1(..) , parseJSON1 + , omittedField1 , FromJSON2(..) , parseJSON2 + , omittedField2 , ToJSON1(..) , toJSON1 , toEncoding1 + , omitField1 , ToJSON2(..) , toJSON2 , toEncoding2 + , omitField2 -- * Generic JSON classes , GFromJSON(..) , FromArgs(..) @@ -67,6 +71,7 @@ module Data.Aeson.Types.Class , genericFromJSONKey -- * Object key-value pairs , KeyValue(..) + , KeyValueOmit(..) -- * List functions , listEncoding @@ -89,14 +94,20 @@ module Data.Aeson.Types.Class , parseField , parseFieldMaybe , parseFieldMaybe' + , parseFieldOmit + , parseFieldOmit' , explicitParseField , explicitParseFieldMaybe , explicitParseFieldMaybe' + , explicitParseFieldOmit + , explicitParseFieldOmit' -- ** Operators , (.:) , (.:?) , (.:!) , (.!=) + , (.:?=) + , (.:!=) ) where diff --git a/src/Data/Aeson/Types/FromJSON.hs b/src/Data/Aeson/Types/FromJSON.hs index 7417ca4d3..074ff113c 100644 --- a/src/Data/Aeson/Types/FromJSON.hs +++ b/src/Data/Aeson/Types/FromJSON.hs @@ -10,6 +10,7 @@ {-# LANGUAGE PolyKinds #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE UndecidableInstances #-} @@ -25,8 +26,10 @@ module Data.Aeson.Types.FromJSON -- * Liftings to unary and binary type constructors , FromJSON1(..) , parseJSON1 + , omittedField1 , FromJSON2(..) , parseJSON2 + , omittedField2 -- * Generic JSON classes , GFromJSON(..) , FromArgs(..) @@ -61,23 +64,28 @@ module Data.Aeson.Types.FromJSON , parseField , parseFieldMaybe , parseFieldMaybe' + , parseFieldOmit + , parseFieldOmit' , explicitParseField , explicitParseFieldMaybe , explicitParseFieldMaybe' + , explicitParseFieldOmit + , explicitParseFieldOmit' , parseIndexedJSON -- ** Operators , (.:) , (.:?) , (.:!) , (.!=) - + , (.:?=) + , (.:!=) -- * Internal , parseOptionalFieldWith ) where import Data.Aeson.Internal.Prelude -import Control.Monad (zipWithM) +import Control.Monad (zipWithM, guard) import Data.Aeson.Internal.Functions (mapKey, mapKeyO) import Data.Aeson.Internal.Scientific import Data.Aeson.Types.Generic @@ -237,11 +245,11 @@ class GFromJSON arity f where gParseJSON :: Options -> FromArgs arity a -> Value -> Parser (f a) -- | A 'FromArgs' value either stores nothing (for 'FromJSON') or it stores the --- two function arguments that decode occurrences of the type parameter (for +-- three function arguments that decode occurrences of the type parameter (for -- 'FromJSON1'). data FromArgs arity a where NoFromArgs :: FromArgs Zero a - From1Args :: (Value -> Parser a) -> (Value -> Parser [a]) -> FromArgs One a + From1Args :: Maybe a -> (Value -> Parser a) -> (Value -> Parser [a]) -> FromArgs One a -- | A configurable generic JSON decoder. This function applied to -- 'defaultOptions' is used as the default for 'parseJSON' when the @@ -254,9 +262,9 @@ genericParseJSON opts = fmap to . gParseJSON opts NoFromArgs -- 'defaultOptions' is used as the default for 'liftParseJSON' when the -- type is an instance of 'Generic1'. genericLiftParseJSON :: (Generic1 f, GFromJSON One (Rep1 f)) - => Options -> (Value -> Parser a) -> (Value -> Parser [a]) + => Options -> Maybe a -> (Value -> Parser a) -> (Value -> Parser [a]) -> Value -> Parser (f a) -genericLiftParseJSON opts pj pjl = fmap to1 . gParseJSON opts (From1Args pj pjl) +genericLiftParseJSON opts o pj pjl = fmap to1 . gParseJSON opts (From1Args o pj pjl) ------------------------------------------------------------------------------- -- Class @@ -379,6 +387,14 @@ class FromJSON a where . V.toList $ a + -- | Default value for optional fields. + -- Used by @('.:?=')@ operator, and Generics and TH deriving + -- with @'allowOmittedFields' = True@ (default). + -- + -- @since 2.2.0.0 + omittedField :: Maybe a + omittedField = Nothing + -- | @since 2.1.0.0 instance (Generic a, GFromJSON Zero (Rep a)) => FromJSON (Generically a) where parseJSON = coerce (genericParseJSON defaultOptions :: Value -> Parser a) @@ -591,25 +607,32 @@ typeOf v = case v of -- 'liftParseJSON' = 'genericLiftParseJSON' customOptions -- @ class FromJSON1 f where - liftParseJSON :: (Value -> Parser a) -> (Value -> Parser [a]) -> Value -> Parser (f a) + liftParseJSON :: Maybe a -> (Value -> Parser a) -> (Value -> Parser [a]) -> Value -> Parser (f a) default liftParseJSON :: (Generic1 f, GFromJSON One (Rep1 f)) - => (Value -> Parser a) -> (Value -> Parser [a]) -> Value -> Parser (f a) + => Maybe a -> (Value -> Parser a) -> (Value -> Parser [a]) -> Value -> Parser (f a) liftParseJSON = genericLiftParseJSON defaultOptions - liftParseJSONList :: (Value -> Parser a) -> (Value -> Parser [a]) -> Value -> Parser [f a] - liftParseJSONList f g v = listParser (liftParseJSON f g) v + liftParseJSONList :: Maybe a -> (Value -> Parser a) -> (Value -> Parser [a]) -> Value -> Parser [f a] + liftParseJSONList o f g v = listParser (liftParseJSON o f g) v + + liftOmittedField :: Maybe a -> Maybe (f a) + liftOmittedField _ = Nothing -- | @since 2.1.0.0 instance (Generic1 f, GFromJSON One (Rep1 f)) => FromJSON1 (Generically1 f) where - liftParseJSON :: forall a. (Value -> Parser a) -> (Value -> Parser [a]) -> Value -> Parser (Generically1 f a) - liftParseJSON = coerce (genericLiftParseJSON defaultOptions :: (Value -> Parser a) -> (Value -> Parser [a]) -> Value -> Parser (f a)) + liftParseJSON :: forall a. Maybe a -> (Value -> Parser a) -> (Value -> Parser [a]) -> Value -> Parser (Generically1 f a) + liftParseJSON = coerce (genericLiftParseJSON defaultOptions :: Maybe a -> (Value -> Parser a) -> (Value -> Parser [a]) -> Value -> Parser (f a)) -- | Lift the standard 'parseJSON' function through the type constructor. parseJSON1 :: (FromJSON1 f, FromJSON a) => Value -> Parser (f a) -parseJSON1 = liftParseJSON parseJSON parseJSONList +parseJSON1 = liftParseJSON omittedField parseJSON parseJSONList {-# INLINE parseJSON1 #-} +-- | @since 2.2.0.0 +omittedField1 :: (FromJSON1 f, FromJSON a) => Maybe (f a) +omittedField1 = liftOmittedField omittedField + -- | Lifting of the 'FromJSON' class to binary type constructors. -- -- Instead of manually writing your 'FromJSON2' instance, "Data.Aeson.TH" @@ -619,25 +642,36 @@ parseJSON1 = liftParseJSON parseJSON parseJSONList -- unlike 'parseJSON' and 'liftParseJSON'. class FromJSON2 f where liftParseJSON2 - :: (Value -> Parser a) + :: Maybe a + -> (Value -> Parser a) -> (Value -> Parser [a]) + -> Maybe b -> (Value -> Parser b) -> (Value -> Parser [b]) -> Value -> Parser (f a b) liftParseJSONList2 - :: (Value -> Parser a) + :: Maybe a + -> (Value -> Parser a) -> (Value -> Parser [a]) + -> Maybe b -> (Value -> Parser b) -> (Value -> Parser [b]) -> Value -> Parser [f a b] - liftParseJSONList2 fa ga fb gb = withArray "[]" $ \vals -> - fmap V.toList (V.mapM (liftParseJSON2 fa ga fb gb) vals) + liftParseJSONList2 oa fa ga ob fb gb = withArray "[]" $ \vals -> + fmap V.toList (V.mapM (liftParseJSON2 oa fa ga ob fb gb) vals) + + liftOmittedField2 :: Maybe a -> Maybe b -> Maybe (f a b) + liftOmittedField2 _ _ = Nothing -- | Lift the standard 'parseJSON' function through the type constructor. parseJSON2 :: (FromJSON2 f, FromJSON a, FromJSON b) => Value -> Parser (f a b) -parseJSON2 = liftParseJSON2 parseJSON parseJSONList parseJSON parseJSONList +parseJSON2 = liftParseJSON2 omittedField parseJSON parseJSONList omittedField parseJSON parseJSONList {-# INLINE parseJSON2 #-} +-- | @since 2.2.0.0 +omittedField2 :: (FromJSON2 f, FromJSON a, FromJSON b) => Maybe (f a b) +omittedField2 = liftOmittedField2 omittedField omittedField + ------------------------------------------------------------------------------- -- List functions ------------------------------------------------------------------------------- @@ -653,7 +687,7 @@ listParser _ v = typeMismatch "Array" v ------------------------------------------------------------------------------- instance FromJSON1 [] where - liftParseJSON _ p' = p' + liftParseJSON _ _ p' = p' instance (FromJSON a) => FromJSON [a] where parseJSON = parseJSON1 @@ -828,6 +862,25 @@ ifromJSON = iparse parseJSON (.:!) :: (FromJSON a) => Object -> Key -> Parser (Maybe a) (.:!) = explicitParseFieldMaybe' parseJSON +-- | Retrieve the value associated with the given key of an 'Object'. +-- If the key is not present and the 'omittedField' is @'Just' x@ for some @x@, +-- the result will be that @x@. +-- +-- @since 2.2.0.0 +(.:?=) :: (FromJSON a) => Object -> Key -> Parser a +(.:?=) = explicitParseFieldOmit omittedField parseJSON + +-- | Retrieve the value associated with the given key of an 'Object'. +-- If the key is not present or the field is @null@ and the 'omittedField' is @'Just' x@ for some @x@, +-- the result will be that @x@. +-- +-- This differs from '.:?=' by attempting to parse 'Null' the same as any +-- other JSON value, instead of using 'omittedField' when it's 'Just'. +-- +-- @since 2.2.0.0 +(.:!=) :: (FromJSON a) => Object -> Key -> Parser a +(.:!=) = explicitParseFieldOmit' omittedField parseJSON + -- | Function variant of '.:'. parseField :: (FromJSON a) => Object -> Key -> Parser a parseField = (.:) @@ -840,6 +893,18 @@ parseFieldMaybe = (.:?) parseFieldMaybe' :: (FromJSON a) => Object -> Key -> Parser (Maybe a) parseFieldMaybe' = (.:!) +-- | Function variant of '.:?='. +-- +-- @since 2.2.0.0 +parseFieldOmit :: (FromJSON a) => Object -> Key -> Parser a +parseFieldOmit = (.:?=) + +-- | Function variant of '.:!='. +-- +-- @since 2.2.0.0 +parseFieldOmit' :: (FromJSON a) => Object -> Key -> Parser a +parseFieldOmit' = (.:!=) + -- | Variant of '.:' with explicit parser function. -- -- E.g. @'explicitParseField' 'parseJSON1' :: ('FromJSON1' f, 'FromJSON' a) -> 'Object' -> 'Text' -> 'Parser' (f a)@ @@ -852,7 +917,7 @@ explicitParseField p obj key = case KM.lookup key obj of explicitParseFieldMaybe :: (Value -> Parser a) -> Object -> Key -> Parser (Maybe a) explicitParseFieldMaybe p obj key = case KM.lookup key obj of Nothing -> pure Nothing - Just v -> liftParseJSON p (listParser p) v Key key -- listParser isn't used by maybe instance. + Just v -> liftParseJSON Nothing p (listParser p) v Key key -- listParser isn't used by maybe instance. -- | Variant of '.:!' with explicit parser function. explicitParseFieldMaybe' :: (Value -> Parser a) -> Object -> Key -> Parser (Maybe a) @@ -860,6 +925,20 @@ explicitParseFieldMaybe' p obj key = case KM.lookup key obj of Nothing -> pure Nothing Just v -> Just <$> p v Key key +-- | Variant of '.:?=' with explicit arguments. +-- +-- @since 2.2.0.0 +explicitParseFieldOmit :: Maybe a -> (Value -> Parser a) -> Object -> Key -> Parser a +explicitParseFieldOmit Nothing p obj key = explicitParseField p obj key +explicitParseFieldOmit (Just def) p obj key = explicitParseFieldMaybe p obj key .!= def + +-- | Variant of '.:!=' with explicit arguments. +-- +-- @since 2.2.0.0 +explicitParseFieldOmit' :: Maybe a -> (Value -> Parser a) -> Object -> Key -> Parser a +explicitParseFieldOmit' Nothing p obj key = explicitParseField p obj key +explicitParseFieldOmit' (Just def) p obj key = explicitParseFieldMaybe' p obj key .!= def + -- | Helper for use in combination with '.:?' to provide default -- values for optional JSON object fields. -- @@ -932,22 +1011,25 @@ instance (FromJSON a) => GFromJSON arity (K1 i a) where instance GFromJSON One Par1 where -- Direct occurrences of the last type parameter are decoded with the -- function passed in as an argument: - gParseJSON _opts (From1Args pj _) = fmap Par1 . pj + gParseJSON _opts (From1Args _ pj _) = fmap Par1 . pj {-# INLINE gParseJSON #-} instance (FromJSON1 f) => GFromJSON One (Rec1 f) where -- Recursive occurrences of the last type parameter are decoded using their -- FromJSON1 instance: - gParseJSON _opts (From1Args pj pjl) = fmap Rec1 . liftParseJSON pj pjl + gParseJSON _opts (From1Args o pj pjl) = fmap Rec1 . liftParseJSON o pj pjl {-# INLINE gParseJSON #-} instance (FromJSON1 f, GFromJSON One g) => GFromJSON One (f :.: g) where -- If an occurrence of the last type parameter is nested inside two -- composed types, it is decoded by using the outermost type's FromJSON1 -- instance to generically decode the innermost type: + -- + -- Note: the ommitedField is not passed here. + -- This might be related for :.: associated the wrong way in Generics Rep. gParseJSON opts fargs = - let gpj = gParseJSON opts fargs in - fmap Comp1 . liftParseJSON gpj (listParser gpj) + let gpj = gParseJSON opts fargs + in fmap Comp1 . liftParseJSON Nothing gpj (listParser gpj) {-# INLINE gParseJSON #-} -------------------------------------------------------------------------------- @@ -1336,34 +1418,56 @@ instance ( RecordFromJSON' arity a <*> recordParseJSON' p obj {-# INLINE recordParseJSON' #-} -instance {-# OVERLAPPABLE #-} (Selector s, GFromJSON arity a) => - RecordFromJSON' arity (S1 s a) where - recordParseJSON' (cname :* tname :* opts :* fargs) obj = do - fv <- contextCons cname tname (obj .: label) - M1 <$> gParseJSON opts fargs fv Key label - where - label = Key.fromString $ fieldLabelModifier opts sname - sname = selName (undefined :: M1 _i s _f _p) +instance {-# OVERLAPPABLE #-} + RecordFromJSON' arity f => RecordFromJSON' arity (M1 i s f) where + recordParseJSON' args obj = M1 <$> recordParseJSON' args obj {-# INLINE recordParseJSON' #-} -instance {-# INCOHERENT #-} (Selector s, FromJSON a) => - RecordFromJSON' arity (S1 s (K1 i (Maybe a))) where - recordParseJSON' (_ :* _ :* opts :* _) obj = M1 . K1 <$> obj .:? label - where - label = Key.fromString $ fieldLabelModifier opts sname - sname = selName (undefined :: M1 _i s _f _p) +instance (Selector s, FromJSON a, Generic a, K1 i a ~ Rep a) => + RecordFromJSON' arity (S1 s (K1 i a)) where + recordParseJSON' args@(_ :* _ :* opts :* _) obj = + recordParseJSONImpl (guard (allowOmittedFields opts) >> fmap K1 omittedField) gParseJSON args obj {-# INLINE recordParseJSON' #-} -#if !MIN_VERSION_base(4,16,0) --- Parse an Option like a Maybe. -instance {-# INCOHERENT #-} (Selector s, FromJSON a) => - RecordFromJSON' arity (S1 s (K1 i (Semigroup.Option a))) where - recordParseJSON' p obj = wrap <$> recordParseJSON' p obj - where - wrap :: S1 s (K1 i (Maybe a)) p -> S1 s (K1 i (Semigroup.Option a)) p - wrap (M1 (K1 a)) = M1 (K1 (Semigroup.Option a)) +instance {-# OVERLAPPING #-} + (Selector s, FromJSON a) => + RecordFromJSON' arity (S1 s (Rec0 a)) where + recordParseJSON' args@(_ :* _ :* opts :* _) obj = + recordParseJSONImpl (guard (allowOmittedFields opts) >> fmap K1 omittedField) gParseJSON args obj {-# INLINE recordParseJSON' #-} -#endif + +instance {-# OVERLAPPING #-} + (Selector s, GFromJSON One (Rec1 f), FromJSON1 f) => + RecordFromJSON' One (S1 s (Rec1 f)) where + recordParseJSON' args@(_ :* _ :* opts :* From1Args o _ _) obj = + recordParseJSONImpl (guard (allowOmittedFields opts) >> fmap Rec1 (liftOmittedField o)) gParseJSON args obj + {-# INLINE recordParseJSON' #-} + +instance {-# OVERLAPPING #-} + (Selector s, GFromJSON One Par1) => + RecordFromJSON' One (S1 s Par1) where + recordParseJSON' args@(_ :* _ :* opts :* From1Args o _ _) obj = + recordParseJSONImpl (guard (allowOmittedFields opts) >> fmap Par1 o) gParseJSON args obj + {-# INLINE recordParseJSON' #-} + + +recordParseJSONImpl :: forall s arity a f i + . (Selector s) + => Maybe (f a) + -> (Options -> FromArgs arity a -> Value -> Parser (f a)) + -> (ConName :* TypeName :* Options :* FromArgs arity a) + -> Object -> Parser (M1 i s f a) +recordParseJSONImpl mdef parseVal (cname :* tname :* opts :* fargs) obj = + handleMissingKey (M1 <$> mdef) $ do + fv <- contextCons cname tname (obj .: label) + M1 <$> parseVal opts fargs fv Key label + where + handleMissingKey Nothing p = p + handleMissingKey (Just def) p = if label `KM.member` obj then p else pure def + + label = Key.fromString $ fieldLabelModifier opts sname + sname = selName (undefined :: M1 _i s _f _p) +{-# INLINE recordParseJSONImpl #-} -------------------------------------------------------------------------------- @@ -1489,28 +1593,33 @@ instance {-# OVERLAPPING #-} instance FromJSON2 Const where - liftParseJSON2 p _ _ _ = fmap Const . p + liftParseJSON2 _ p _ _ _ _ = coerce p + liftOmittedField2 o _ = coerce o instance FromJSON a => FromJSON1 (Const a) where - liftParseJSON _ _ = fmap Const . parseJSON + liftParseJSON _ _ _ = coerce (parseJSON @a) + liftOmittedField _ = coerce (omittedField @a) instance FromJSON a => FromJSON (Const a b) where - parseJSON = fmap Const . parseJSON + parseJSON = coerce (parseJSON @a) + omittedField = coerce (omittedField @a) instance (FromJSON a, FromJSONKey a) => FromJSONKey (Const a b) where - fromJSONKey = fmap Const fromJSONKey + fromJSONKey = coerce (fromJSONKey @a) instance FromJSON1 Maybe where - liftParseJSON _ _ Null = pure Nothing - liftParseJSON p _ a = Just <$> p a + liftParseJSON _ _ _ Null = pure Nothing + liftParseJSON _ p _ a = Just <$> p a + + liftOmittedField _ = Just Nothing instance (FromJSON a) => FromJSON (Maybe a) where parseJSON = parseJSON1 - + omittedField = omittedField1 instance FromJSON2 Either where - liftParseJSON2 pA _ pB _ (Object (KM.toList -> [(key, value)])) + liftParseJSON2 _ pA _ _ pB _ (Object (KM.toList -> [(key, value)])) | key == left = Left <$> pA value Key left | key == right = Right <$> pB value Key right where @@ -1518,13 +1627,13 @@ instance FromJSON2 Either where left = "Left" right = "Right" - liftParseJSON2 _ _ _ _ _ = fail $ + liftParseJSON2 _ _ _ _ _ _ _ = fail $ "expected an object with a single property " ++ "where the property key should be either " ++ "\"Left\" or \"Right\"" instance (FromJSON a) => FromJSON1 (Either a) where - liftParseJSON = liftParseJSON2 parseJSON parseJSONList + liftParseJSON = liftParseJSON2 omittedField parseJSON parseJSONList instance (FromJSON a, FromJSON b) => FromJSON (Either a b) where parseJSON = parseJSON2 @@ -1557,6 +1666,7 @@ instance FromJSON Ordering where instance FromJSON () where parseJSON _ = pure () + omittedField = Just () instance FromJSON Char where parseJSON = withText "Char" parseChar @@ -1745,7 +1855,7 @@ parseVersionText = go . readP_to_S parseVersion . unpack ------------------------------------------------------------------------------- instance FromJSON1 NonEmpty where - liftParseJSON p _ = withArray "NonEmpty" $ + liftParseJSON _ p _ = withArray "NonEmpty" $ (>>= ne) . Tr.sequence . zipWith (parseIndexedJSON p) [0..] . V.toList where ne [] = fail "parsing NonEmpty failed, unexpected empty list" @@ -1766,7 +1876,7 @@ instance FromJSON Scientific where ------------------------------------------------------------------------------- instance FromJSON1 DList.DList where - liftParseJSON p _ = withArray "DList" $ + liftParseJSON _ p _ = withArray "DList" $ fmap DList.fromList . Tr.sequence . zipWith (parseIndexedJSON p) [0..] . V.toList @@ -1775,7 +1885,7 @@ instance (FromJSON a) => FromJSON (DList.DList a) where -- | @since 1.5.3.0 instance FromJSON1 DNE.DNonEmpty where - liftParseJSON p _ = withArray "DNonEmpty" $ + liftParseJSON _ p _ = withArray "DNonEmpty" $ (>>= ne) . Tr.sequence . zipWith (parseIndexedJSON p) [0..] . V.toList where ne [] = fail "parsing DNonEmpty failed, unexpected empty list" @@ -1791,13 +1901,13 @@ instance (FromJSON a) => FromJSON (DNE.DNonEmpty a) where -- | @since 2.0.2.0 instance FromJSON1 Solo where - liftParseJSON p _ a = Solo <$> p a - liftParseJSONList _ p a = fmap Solo <$> p a + liftParseJSON _ p _ a = Solo <$> p a + liftParseJSONList _ _ p a = fmap Solo <$> p a -- | @since 2.0.2.0 instance (FromJSON a) => FromJSON (Solo a) where parseJSON = parseJSON1 - parseJSONList = liftParseJSONList parseJSON parseJSONList + parseJSONList = liftParseJSONList omittedField parseJSON parseJSONList -- | @since 2.0.2.0 instance (FromJSONKey a) => FromJSONKey (Solo a) where @@ -1809,14 +1919,18 @@ instance (FromJSONKey a) => FromJSONKey (Solo a) where ------------------------------------------------------------------------------- instance FromJSON1 Identity where - liftParseJSON p _ a = Identity <$> p a + liftParseJSON _ p _ a = coerce (p a) - liftParseJSONList _ p a = fmap Identity <$> p a + liftParseJSONList _ _ p a = coerce (p a) + + liftOmittedField = coerce instance (FromJSON a) => FromJSON (Identity a) where parseJSON = parseJSON1 - parseJSONList = liftParseJSONList parseJSON parseJSONList + parseJSONList = liftParseJSONList omittedField parseJSON parseJSONList + + omittedField = coerce (omittedField @a) instance (FromJSONKey a) => FromJSONKey (Identity a) where fromJSONKey = coerceFromJSONKeyFunction (fromJSONKey :: FromJSONKeyFunction a) @@ -1824,44 +1938,45 @@ instance (FromJSONKey a) => FromJSONKey (Identity a) where instance (FromJSON1 f, FromJSON1 g) => FromJSON1 (Compose f g) where - liftParseJSON p pl a = Compose <$> liftParseJSON g gl a + liftParseJSON o p pl a = coerce (liftParseJSON @f (liftOmittedField o) g gl a) where - g = liftParseJSON p pl - gl = liftParseJSONList p pl + g = liftParseJSON @g o p pl + gl = liftParseJSONList @g o p pl - liftParseJSONList p pl a = map Compose <$> liftParseJSONList g gl a + liftParseJSONList o p pl a = coerce (liftParseJSONList @f (liftOmittedField o) g gl a) where - g = liftParseJSON p pl - gl = liftParseJSONList p pl + g = liftParseJSON @g o p pl + gl = liftParseJSONList @g o p pl instance (FromJSON1 f, FromJSON1 g, FromJSON a) => FromJSON (Compose f g a) where parseJSON = parseJSON1 - parseJSONList = liftParseJSONList parseJSON parseJSONList - + parseJSONList = liftParseJSONList omittedField parseJSON parseJSONList instance (FromJSON1 f, FromJSON1 g) => FromJSON1 (Product f g) where - liftParseJSON p pl a = uncurry Pair <$> liftParseJSON2 px pxl py pyl a + liftParseJSON o p pl a = uncurry Pair <$> liftParseJSON2 ox px pxl oy py pyl a where - px = liftParseJSON p pl - pxl = liftParseJSONList p pl - py = liftParseJSON p pl - pyl = liftParseJSONList p pl + ox = liftOmittedField o + px = liftParseJSON o p pl + pxl = liftParseJSONList o p pl + oy = liftOmittedField o + py = liftParseJSON o p pl + pyl = liftParseJSONList o p pl instance (FromJSON1 f, FromJSON1 g, FromJSON a) => FromJSON (Product f g a) where parseJSON = parseJSON1 instance (FromJSON1 f, FromJSON1 g) => FromJSON1 (Sum f g) where - liftParseJSON p pl (Object (KM.toList -> [(key, value)])) - | key == inl = InL <$> liftParseJSON p pl value Key inl - | key == inr = InR <$> liftParseJSON p pl value Key inr + liftParseJSON o p pl (Object (KM.toList -> [(key, value)])) + | key == inl = InL <$> liftParseJSON o p pl value Key inl + | key == inr = InR <$> liftParseJSON o p pl value Key inr where inl, inr :: Key inl = "InL" inr = "InR" - liftParseJSON _ _ _ = fail $ + liftParseJSON _ _ _ _ = fail $ "parsing Sum failed, expected an object with a single property " ++ "where the property key should be either " ++ "\"InL\" or \"InR\"" @@ -1874,7 +1989,7 @@ instance (FromJSON1 f, FromJSON1 g, FromJSON a) => FromJSON (Sum f g a) where ------------------------------------------------------------------------------- instance FromJSON1 Seq.Seq where - liftParseJSON p _ = withArray "Seq" $ + liftParseJSON _ p _ = withArray "Seq" $ fmap Seq.fromList . Tr.sequence . zipWith (parseIndexedJSON p) [0..] . V.toList @@ -1891,18 +2006,19 @@ instance FromJSON IntSet.IntSet where instance FromJSON1 IntMap.IntMap where - liftParseJSON p pl = fmap IntMap.fromList . liftParseJSON p' pl' + liftParseJSON o p pl = fmap IntMap.fromList . liftParseJSON o' p' pl' where - p' = liftParseJSON2 parseJSON parseJSONList p pl - pl' = liftParseJSONList2 parseJSON parseJSONList p pl + o' = liftOmittedField o + p' = liftParseJSON o p pl + pl' = liftParseJSONList o p pl instance FromJSON a => FromJSON (IntMap.IntMap a) where parseJSON = fmap IntMap.fromList . parseJSON instance (FromJSONKey k, Ord k) => FromJSON1 (M.Map k) where - liftParseJSON :: forall a. (Value -> Parser a) -> (Value -> Parser [a]) -> Value -> Parser (M.Map k a) - liftParseJSON p _ = case fromJSONKey of + liftParseJSON :: forall a. Maybe a -> (Value -> Parser a) -> (Value -> Parser [a]) -> Value -> Parser (M.Map k a) + liftParseJSON _ p _ = case fromJSONKey of FromJSONKeyCoerce -> withObject "Map ~Text" $ case Key.coercionToText of Nothing -> text coerce Just Coercion -> case KM.coercionToMap of @@ -1933,12 +2049,13 @@ instance (FromJSONKey k, Ord k, FromJSON v) => FromJSON (M.Map k v) where instance FromJSON1 Tree.Tree where - liftParseJSON p pl = go + liftParseJSON o p pl = go where - go v = uncurry Tree.Node <$> liftParseJSON2 p pl p' pl' v + go v = uncurry Tree.Node <$> liftParseJSON2 o p pl o' p' pl' v - p' = liftParseJSON go (listParser go) - pl'= liftParseJSONList go (listParser go) + o' = Nothing + p' = liftParseJSON Nothing go (listParser go) + pl'= liftParseJSONList Nothing go (listParser go) instance (FromJSON v) => FromJSON (Tree.Tree v) where parseJSON = parseJSON1 @@ -1960,7 +2077,7 @@ instance FromJSONKey UUID.UUID where ------------------------------------------------------------------------------- instance FromJSON1 Vector where - liftParseJSON p _ = withArray "Vector" $ + liftParseJSON _ p _ = withArray "Vector" $ V.mapM (uncurry $ parseIndexedJSON p) . V.indexed instance (FromJSON a) => FromJSON (Vector a) where @@ -1988,8 +2105,8 @@ instance (Eq a, Hashable a, FromJSON a) => FromJSON (HashSet.HashSet a) where instance (FromJSONKey k, Eq k, Hashable k) => FromJSON1 (H.HashMap k) where - liftParseJSON :: forall a. (Value -> Parser a) -> (Value -> Parser [a]) -> Value -> Parser (H.HashMap k a) - liftParseJSON p _ = case fromJSONKey of + liftParseJSON :: forall a. Maybe a -> (Value -> Parser a) -> (Value -> Parser [a]) -> Value -> Parser (H.HashMap k a) + liftParseJSON _ p _ = case fromJSONKey of FromJSONKeyCoerce -> withObject "HashMap ~Text" $ case Key.coercionToText of Nothing -> text coerce Just Coercion -> case KM.coercionToHashMap of @@ -2039,7 +2156,7 @@ instance FromJSONKey Key where -- | @since 2.0.1.0 instance FromJSON1 KM.KeyMap where - liftParseJSON p _ = withObject "KeyMap" $ \obj -> + liftParseJSON _ p _ = withObject "KeyMap" $ \obj -> traverse p obj -- | @since 2.0.1.0 @@ -2194,86 +2311,100 @@ instance FromJSONKey Month where ------------------------------------------------------------------------------- instance FromJSON1 Monoid.Dual where - liftParseJSON p _ = fmap Monoid.Dual . p + liftParseJSON _ p _ = coerce p + + liftOmittedField = coerce instance FromJSON a => FromJSON (Monoid.Dual a) where parseJSON = parseJSON1 instance FromJSON1 Monoid.First where - liftParseJSON p p' = fmap Monoid.First . liftParseJSON p p' + liftParseJSON o = coerce (liftParseJSON @Maybe o) + liftOmittedField _ = Just (Monoid.First Nothing) instance FromJSON a => FromJSON (Monoid.First a) where parseJSON = parseJSON1 - + omittedField = omittedField1 instance FromJSON1 Monoid.Last where - liftParseJSON p p' = fmap Monoid.Last . liftParseJSON p p' + liftParseJSON o = coerce (liftParseJSON @Maybe o) + liftOmittedField _ = Just (Monoid.Last Nothing) instance FromJSON a => FromJSON (Monoid.Last a) where parseJSON = parseJSON1 - + omittedField = omittedField1 instance FromJSON1 Semigroup.Min where - liftParseJSON p _ a = Semigroup.Min <$> p a + liftParseJSON _ p _ a = coerce (p a) - liftParseJSONList _ p a = fmap Semigroup.Min <$> p a + liftParseJSONList _ _ p a = coerce (p a) + + liftOmittedField = coerce instance (FromJSON a) => FromJSON (Semigroup.Min a) where parseJSON = parseJSON1 - parseJSONList = liftParseJSONList parseJSON parseJSONList + parseJSONList = liftParseJSONList omittedField parseJSON parseJSONList + omittedField = omittedField1 instance FromJSON1 Semigroup.Max where - liftParseJSON p _ a = Semigroup.Max <$> p a + liftParseJSON _ p _ a = coerce (p a) - liftParseJSONList _ p a = fmap Semigroup.Max <$> p a + liftParseJSONList _ _ p a = coerce (p a) + liftOmittedField = coerce instance (FromJSON a) => FromJSON (Semigroup.Max a) where parseJSON = parseJSON1 - parseJSONList = liftParseJSONList parseJSON parseJSONList - + parseJSONList = liftParseJSONList omittedField parseJSON parseJSONList + omittedField = omittedField1 instance FromJSON1 Semigroup.First where - liftParseJSON p _ a = Semigroup.First <$> p a + liftParseJSON _ p _ a = coerce (p a) - liftParseJSONList _ p a = fmap Semigroup.First <$> p a + liftParseJSONList _ _ p a = coerce (p a) + liftOmittedField = coerce instance (FromJSON a) => FromJSON (Semigroup.First a) where parseJSON = parseJSON1 - parseJSONList = liftParseJSONList parseJSON parseJSONList + parseJSONList = liftParseJSONList omittedField parseJSON parseJSONList instance FromJSON1 Semigroup.Last where - liftParseJSON p _ a = Semigroup.Last <$> p a + liftParseJSON _ p _ a = coerce (p a) - liftParseJSONList _ p a = fmap Semigroup.Last <$> p a + liftParseJSONList _ _ p a = coerce (p a) + liftOmittedField = coerce instance (FromJSON a) => FromJSON (Semigroup.Last a) where parseJSON = parseJSON1 - parseJSONList = liftParseJSONList parseJSON parseJSONList - + parseJSONList = liftParseJSONList omittedField parseJSON parseJSONList + omittedField = omittedField1 instance FromJSON1 Semigroup.WrappedMonoid where - liftParseJSON p _ a = Semigroup.WrapMonoid <$> p a + liftParseJSON _ p _ a = coerce (p a) - liftParseJSONList _ p a = fmap Semigroup.WrapMonoid <$> p a + liftParseJSONList _ _ p a = coerce (p a) + liftOmittedField = coerce instance (FromJSON a) => FromJSON (Semigroup.WrappedMonoid a) where parseJSON = parseJSON1 - parseJSONList = liftParseJSONList parseJSON parseJSONList + parseJSONList = liftParseJSONList omittedField parseJSON parseJSONList + omittedField = omittedField1 #if !MIN_VERSION_base(4,16,0) instance FromJSON1 Semigroup.Option where - liftParseJSON p p' = fmap Semigroup.Option . liftParseJSON p p' + liftParseJSON o = coerce (liftParseJSON @Maybe o) + liftOmittedField _ = Just (Semigroup.Option Nothing) instance FromJSON a => FromJSON (Semigroup.Option a) where parseJSON = parseJSON1 + omittedField = omittedField1 #endif ------------------------------------------------------------------------------- @@ -2282,7 +2413,7 @@ instance FromJSON a => FromJSON (Semigroup.Option a) where -- | @since 1.5.3.0 instance FromJSON1 f => FromJSON (F.Fix f) where - parseJSON = go where go = fmap F.Fix . liftParseJSON go parseJSONList + parseJSON = go where go = coerce (liftParseJSON @f Nothing go parseJSONList) -- | @since 1.5.3.0 instance (FromJSON1 f, Functor f) => FromJSON (F.Mu f) where @@ -2302,11 +2433,11 @@ instance (FromJSON a, FromJSON b) => FromJSON (S.These a b) where -- | @since 1.5.3.0 instance FromJSON2 S.These where - liftParseJSON2 pa pas pb pbs = fmap S.toStrict . liftParseJSON2 pa pas pb pbs + liftParseJSON2 oa pa pas ob pb pbs = fmap S.toStrict . liftParseJSON2 oa pa pas ob pb pbs -- | @since 1.5.3.0 instance FromJSON a => FromJSON1 (S.These a) where - liftParseJSON pa pas = fmap S.toStrict . liftParseJSON pa pas + liftParseJSON oa pa pas = fmap S.toStrict . liftParseJSON oa pa pas -- | @since 1.5.3.0 instance (FromJSON a, FromJSON b) => FromJSON (S.Pair a b) where @@ -2314,11 +2445,11 @@ instance (FromJSON a, FromJSON b) => FromJSON (S.Pair a b) where -- | @since 1.5.3.0 instance FromJSON2 S.Pair where - liftParseJSON2 pa pas pb pbs = fmap S.toStrict . liftParseJSON2 pa pas pb pbs + liftParseJSON2 oa pa pas ob pb pbs = fmap S.toStrict . liftParseJSON2 oa pa pas ob pb pbs -- | @since 1.5.3.0 instance FromJSON a => FromJSON1 (S.Pair a) where - liftParseJSON pa pas = fmap S.toStrict . liftParseJSON pa pas + liftParseJSON oa pa pas = fmap S.toStrict . liftParseJSON oa pa pas -- | @since 1.5.3.0 instance (FromJSON a, FromJSON b) => FromJSON (S.Either a b) where @@ -2326,38 +2457,45 @@ instance (FromJSON a, FromJSON b) => FromJSON (S.Either a b) where -- | @since 1.5.3.0 instance FromJSON2 S.Either where - liftParseJSON2 pa pas pb pbs = fmap S.toStrict . liftParseJSON2 pa pas pb pbs + liftParseJSON2 oa pa pas ob pb pbs = fmap S.toStrict . liftParseJSON2 oa pa pas ob pb pbs -- | @since 1.5.3.0 instance FromJSON a => FromJSON1 (S.Either a) where - liftParseJSON pa pas = fmap S.toStrict . liftParseJSON pa pas + liftParseJSON oa pa pas = fmap S.toStrict . liftParseJSON oa pa pas -- | @since 1.5.3.0 instance FromJSON a => FromJSON (S.Maybe a) where parseJSON = fmap S.toStrict . parseJSON + omittedField = fmap S.toStrict omittedField -- | @since 1.5.3.0 instance FromJSON1 S.Maybe where - liftParseJSON pa pas = fmap S.toStrict . liftParseJSON pa pas + liftParseJSON oa pa pas = fmap S.toStrict . liftParseJSON oa pa pas + liftOmittedField = fmap S.toStrict . liftOmittedField ------------------------------------------------------------------------------- -- tagged ------------------------------------------------------------------------------- instance FromJSON1 Proxy where - liftParseJSON _ _ _ = pure Proxy + liftParseJSON _ _ _ _ = pure Proxy + liftOmittedField _ = Just Proxy instance FromJSON (Proxy a) where parseJSON _ = pure Proxy + omittedField = Just Proxy instance FromJSON2 Tagged where - liftParseJSON2 _ _ p _ = fmap Tagged . p + liftParseJSON2 _ _ _ _ p _ = coerce p + liftOmittedField2 _ = coerce instance FromJSON1 (Tagged a) where - liftParseJSON p _ = fmap Tagged . p + liftParseJSON _ p _ = coerce p + liftOmittedField = coerce instance FromJSON b => FromJSON (Tagged a b) where parseJSON = parseJSON1 + omittedField = coerce (omittedField @b) instance FromJSONKey b => FromJSONKey (Tagged a b) where fromJSONKey = coerceFromJSONKeyFunction (fromJSONKey :: FromJSONKeyFunction b) @@ -2379,7 +2517,7 @@ instance (FromJSON a, FromJSON b) => FromJSON (These a b) where -- | @since 1.5.1.0 instance FromJSON a => FromJSON1 (These a) where - liftParseJSON pb _ = withObject "These a b" (p . KM.toList) + liftParseJSON _ pb _ = withObject "These a b" (p . KM.toList) where p [("This", a), ("That", b)] = These <$> parseJSON a <*> pb b p [("That", b), ("This", a)] = These <$> parseJSON a <*> pb b @@ -2389,7 +2527,7 @@ instance FromJSON a => FromJSON1 (These a) where -- | @since 1.5.1.0 instance FromJSON2 These where - liftParseJSON2 pa _ pb _ = withObject "These a b" (p . KM.toList) + liftParseJSON2 _ pa _ _ pb _ = withObject "These a b" (p . KM.toList) where p [("This", a), ("That", b)] = These <$> pa a <*> pb b p [("That", b), ("This", a)] = These <$> pa a <*> pb b @@ -2399,12 +2537,12 @@ instance FromJSON2 These where -- | @since 1.5.1.0 instance (FromJSON1 f, FromJSON1 g) => FromJSON1 (These1 f g) where - liftParseJSON px pl = withObject "These1" (p . KM.toList) + liftParseJSON o px pl = withObject "These1" (p . KM.toList) where - p [("This", a), ("That", b)] = These1 <$> liftParseJSON px pl a <*> liftParseJSON px pl b - p [("That", b), ("This", a)] = These1 <$> liftParseJSON px pl a <*> liftParseJSON px pl b - p [("This", a)] = This1 <$> liftParseJSON px pl a - p [("That", b)] = That1 <$> liftParseJSON px pl b + p [("This", a), ("That", b)] = These1 <$> liftParseJSON o px pl a <*> liftParseJSON o px pl b + p [("That", b), ("This", a)] = These1 <$> liftParseJSON o px pl a <*> liftParseJSON o px pl b + p [("This", a)] = This1 <$> liftParseJSON o px pl a + p [("That", b)] = That1 <$> liftParseJSON o px pl b p _ = fail "Expected object with 'This' and 'That' keys only" -- | @since 1.5.1.0 @@ -2431,7 +2569,7 @@ instance (FromJSONKey a, FromJSON a) => FromJSONKey [a] where ------------------------------------------------------------------------------- instance FromJSON2 (,) where - liftParseJSON2 pA _ pB _ = withArray "(a, b)" $ \t -> + liftParseJSON2 _ pA _ _ pB _ = withArray "(a, b)" $ \t -> let n = V.length t in if n == 2 then (,) @@ -2440,14 +2578,14 @@ instance FromJSON2 (,) where else fail $ "cannot unpack array of length " ++ show n ++ " into a tuple of length 2" instance (FromJSON a) => FromJSON1 ((,) a) where - liftParseJSON = liftParseJSON2 parseJSON parseJSONList + liftParseJSON = liftParseJSON2 omittedField parseJSON parseJSONList instance (FromJSON a, FromJSON b) => FromJSON (a, b) where parseJSON = parseJSON2 instance (FromJSON a) => FromJSON2 ((,,) a) where - liftParseJSON2 pB _ pC _ = withArray "(a, b, c)" $ \t -> + liftParseJSON2 _ pB _ _ pC _ = withArray "(a, b, c)" $ \t -> let n = V.length t in if n == 3 then (,,) @@ -2457,14 +2595,14 @@ instance (FromJSON a) => FromJSON2 ((,,) a) where else fail $ "cannot unpack array of length " ++ show n ++ " into a tuple of length 3" instance (FromJSON a, FromJSON b) => FromJSON1 ((,,) a b) where - liftParseJSON = liftParseJSON2 parseJSON parseJSONList + liftParseJSON = liftParseJSON2 omittedField parseJSON parseJSONList instance (FromJSON a, FromJSON b, FromJSON c) => FromJSON (a, b, c) where parseJSON = parseJSON2 instance (FromJSON a, FromJSON b) => FromJSON2 ((,,,) a b) where - liftParseJSON2 pC _ pD _ = withArray "(a, b, c, d)" $ \t -> + liftParseJSON2 _ pC _ _ pD _ = withArray "(a, b, c, d)" $ \t -> let n = V.length t in if n == 4 then (,,,) @@ -2475,14 +2613,14 @@ instance (FromJSON a, FromJSON b) => FromJSON2 ((,,,) a b) where else fail $ "cannot unpack array of length " ++ show n ++ " into a tuple of length 4" instance (FromJSON a, FromJSON b, FromJSON c) => FromJSON1 ((,,,) a b c) where - liftParseJSON = liftParseJSON2 parseJSON parseJSONList + liftParseJSON = liftParseJSON2 omittedField parseJSON parseJSONList instance (FromJSON a, FromJSON b, FromJSON c, FromJSON d) => FromJSON (a, b, c, d) where parseJSON = parseJSON2 instance (FromJSON a, FromJSON b, FromJSON c) => FromJSON2 ((,,,,) a b c) where - liftParseJSON2 pD _ pE _ = withArray "(a, b, c, d, e)" $ \t -> + liftParseJSON2 _ pD _ _ pE _ = withArray "(a, b, c, d, e)" $ \t -> let n = V.length t in if n == 5 then (,,,,) @@ -2494,14 +2632,14 @@ instance (FromJSON a, FromJSON b, FromJSON c) => FromJSON2 ((,,,,) a b c) where else fail $ "cannot unpack array of length " ++ show n ++ " into a tuple of length 5" instance (FromJSON a, FromJSON b, FromJSON c, FromJSON d) => FromJSON1 ((,,,,) a b c d) where - liftParseJSON = liftParseJSON2 parseJSON parseJSONList + liftParseJSON = liftParseJSON2 omittedField parseJSON parseJSONList instance (FromJSON a, FromJSON b, FromJSON c, FromJSON d, FromJSON e) => FromJSON (a, b, c, d, e) where parseJSON = parseJSON2 instance (FromJSON a, FromJSON b, FromJSON c, FromJSON d) => FromJSON2 ((,,,,,) a b c d) where - liftParseJSON2 pE _ pF _ = withArray "(a, b, c, d, e, f)" $ \t -> + liftParseJSON2 _ pE _ _ pF _ = withArray "(a, b, c, d, e, f)" $ \t -> let n = V.length t in if n == 6 then (,,,,,) @@ -2514,14 +2652,14 @@ instance (FromJSON a, FromJSON b, FromJSON c, FromJSON d) => FromJSON2 ((,,,,,) else fail $ "cannot unpack array of length " ++ show n ++ " into a tuple of length 6" instance (FromJSON a, FromJSON b, FromJSON c, FromJSON d, FromJSON e) => FromJSON1 ((,,,,,) a b c d e) where - liftParseJSON = liftParseJSON2 parseJSON parseJSONList + liftParseJSON = liftParseJSON2 omittedField parseJSON parseJSONList instance (FromJSON a, FromJSON b, FromJSON c, FromJSON d, FromJSON e, FromJSON f) => FromJSON (a, b, c, d, e, f) where parseJSON = parseJSON2 instance (FromJSON a, FromJSON b, FromJSON c, FromJSON d, FromJSON e) => FromJSON2 ((,,,,,,) a b c d e) where - liftParseJSON2 pF _ pG _ = withArray "(a, b, c, d, e, f, g)" $ \t -> + liftParseJSON2 _ pF _ _ pG _ = withArray "(a, b, c, d, e, f, g)" $ \t -> let n = V.length t in if n == 7 then (,,,,,,) @@ -2535,14 +2673,14 @@ instance (FromJSON a, FromJSON b, FromJSON c, FromJSON d, FromJSON e) => FromJSO else fail $ "cannot unpack array of length " ++ show n ++ " into a tuple of length 7" instance (FromJSON a, FromJSON b, FromJSON c, FromJSON d, FromJSON e, FromJSON f) => FromJSON1 ((,,,,,,) a b c d e f) where - liftParseJSON = liftParseJSON2 parseJSON parseJSONList + liftParseJSON = liftParseJSON2 omittedField parseJSON parseJSONList instance (FromJSON a, FromJSON b, FromJSON c, FromJSON d, FromJSON e, FromJSON f, FromJSON g) => FromJSON (a, b, c, d, e, f, g) where parseJSON = parseJSON2 instance (FromJSON a, FromJSON b, FromJSON c, FromJSON d, FromJSON e, FromJSON f) => FromJSON2 ((,,,,,,,) a b c d e f) where - liftParseJSON2 pG _ pH _ = withArray "(a, b, c, d, e, f, g, h)" $ \t -> + liftParseJSON2 _ pG _ _ pH _ = withArray "(a, b, c, d, e, f, g, h)" $ \t -> let n = V.length t in if n == 8 then (,,,,,,,) @@ -2557,14 +2695,14 @@ instance (FromJSON a, FromJSON b, FromJSON c, FromJSON d, FromJSON e, FromJSON f else fail $ "cannot unpack array of length " ++ show n ++ " into a tuple of length 8" instance (FromJSON a, FromJSON b, FromJSON c, FromJSON d, FromJSON e, FromJSON f, FromJSON g) => FromJSON1 ((,,,,,,,) a b c d e f g) where - liftParseJSON = liftParseJSON2 parseJSON parseJSONList + liftParseJSON = liftParseJSON2 omittedField parseJSON parseJSONList instance (FromJSON a, FromJSON b, FromJSON c, FromJSON d, FromJSON e, FromJSON f, FromJSON g, FromJSON h) => FromJSON (a, b, c, d, e, f, g, h) where parseJSON = parseJSON2 instance (FromJSON a, FromJSON b, FromJSON c, FromJSON d, FromJSON e, FromJSON f, FromJSON g) => FromJSON2 ((,,,,,,,,) a b c d e f g) where - liftParseJSON2 pH _ pI _ = withArray "(a, b, c, d, e, f, g, h, i)" $ \t -> + liftParseJSON2 _ pH _ _ pI _ = withArray "(a, b, c, d, e, f, g, h, i)" $ \t -> let n = V.length t in if n == 9 then (,,,,,,,,) @@ -2580,14 +2718,14 @@ instance (FromJSON a, FromJSON b, FromJSON c, FromJSON d, FromJSON e, FromJSON f else fail $ "cannot unpack array of length " ++ show n ++ " into a tuple of length 9" instance (FromJSON a, FromJSON b, FromJSON c, FromJSON d, FromJSON e, FromJSON f, FromJSON g, FromJSON h) => FromJSON1 ((,,,,,,,,) a b c d e f g h) where - liftParseJSON = liftParseJSON2 parseJSON parseJSONList + liftParseJSON = liftParseJSON2 omittedField parseJSON parseJSONList instance (FromJSON a, FromJSON b, FromJSON c, FromJSON d, FromJSON e, FromJSON f, FromJSON g, FromJSON h, FromJSON i) => FromJSON (a, b, c, d, e, f, g, h, i) where parseJSON = parseJSON2 instance (FromJSON a, FromJSON b, FromJSON c, FromJSON d, FromJSON e, FromJSON f, FromJSON g, FromJSON h) => FromJSON2 ((,,,,,,,,,) a b c d e f g h) where - liftParseJSON2 pI _ pJ _ = withArray "(a, b, c, d, e, f, g, h, i, j)" $ \t -> + liftParseJSON2 _ pI _ _ pJ _ = withArray "(a, b, c, d, e, f, g, h, i, j)" $ \t -> let n = V.length t in if n == 10 then (,,,,,,,,,) @@ -2604,14 +2742,14 @@ instance (FromJSON a, FromJSON b, FromJSON c, FromJSON d, FromJSON e, FromJSON f else fail $ "cannot unpack array of length " ++ show n ++ " into a tuple of length 10" instance (FromJSON a, FromJSON b, FromJSON c, FromJSON d, FromJSON e, FromJSON f, FromJSON g, FromJSON h, FromJSON i) => FromJSON1 ((,,,,,,,,,) a b c d e f g h i) where - liftParseJSON = liftParseJSON2 parseJSON parseJSONList + liftParseJSON = liftParseJSON2 omittedField parseJSON parseJSONList instance (FromJSON a, FromJSON b, FromJSON c, FromJSON d, FromJSON e, FromJSON f, FromJSON g, FromJSON h, FromJSON i, FromJSON j) => FromJSON (a, b, c, d, e, f, g, h, i, j) where parseJSON = parseJSON2 instance (FromJSON a, FromJSON b, FromJSON c, FromJSON d, FromJSON e, FromJSON f, FromJSON g, FromJSON h, FromJSON i) => FromJSON2 ((,,,,,,,,,,) a b c d e f g h i) where - liftParseJSON2 pJ _ pK _ = withArray "(a, b, c, d, e, f, g, h, i, j, k)" $ \t -> + liftParseJSON2 _ pJ _ _ pK _ = withArray "(a, b, c, d, e, f, g, h, i, j, k)" $ \t -> let n = V.length t in if n == 11 then (,,,,,,,,,,) @@ -2629,14 +2767,14 @@ instance (FromJSON a, FromJSON b, FromJSON c, FromJSON d, FromJSON e, FromJSON f else fail $ "cannot unpack array of length " ++ show n ++ " into a tuple of length 11" instance (FromJSON a, FromJSON b, FromJSON c, FromJSON d, FromJSON e, FromJSON f, FromJSON g, FromJSON h, FromJSON i, FromJSON j) => FromJSON1 ((,,,,,,,,,,) a b c d e f g h i j) where - liftParseJSON = liftParseJSON2 parseJSON parseJSONList + liftParseJSON = liftParseJSON2 omittedField parseJSON parseJSONList instance (FromJSON a, FromJSON b, FromJSON c, FromJSON d, FromJSON e, FromJSON f, FromJSON g, FromJSON h, FromJSON i, FromJSON j, FromJSON k) => FromJSON (a, b, c, d, e, f, g, h, i, j, k) where parseJSON = parseJSON2 instance (FromJSON a, FromJSON b, FromJSON c, FromJSON d, FromJSON e, FromJSON f, FromJSON g, FromJSON h, FromJSON i, FromJSON j) => FromJSON2 ((,,,,,,,,,,,) a b c d e f g h i j) where - liftParseJSON2 pK _ pL _ = withArray "(a, b, c, d, e, f, g, h, i, j, k, l)" $ \t -> + liftParseJSON2 _ pK _ _ pL _ = withArray "(a, b, c, d, e, f, g, h, i, j, k, l)" $ \t -> let n = V.length t in if n == 12 then (,,,,,,,,,,,) @@ -2655,14 +2793,14 @@ instance (FromJSON a, FromJSON b, FromJSON c, FromJSON d, FromJSON e, FromJSON f else fail $ "cannot unpack array of length " ++ show n ++ " into a tuple of length 12" instance (FromJSON a, FromJSON b, FromJSON c, FromJSON d, FromJSON e, FromJSON f, FromJSON g, FromJSON h, FromJSON i, FromJSON j, FromJSON k) => FromJSON1 ((,,,,,,,,,,,) a b c d e f g h i j k) where - liftParseJSON = liftParseJSON2 parseJSON parseJSONList + liftParseJSON = liftParseJSON2 omittedField parseJSON parseJSONList instance (FromJSON a, FromJSON b, FromJSON c, FromJSON d, FromJSON e, FromJSON f, FromJSON g, FromJSON h, FromJSON i, FromJSON j, FromJSON k, FromJSON l) => FromJSON (a, b, c, d, e, f, g, h, i, j, k, l) where parseJSON = parseJSON2 instance (FromJSON a, FromJSON b, FromJSON c, FromJSON d, FromJSON e, FromJSON f, FromJSON g, FromJSON h, FromJSON i, FromJSON j, FromJSON k) => FromJSON2 ((,,,,,,,,,,,,) a b c d e f g h i j k) where - liftParseJSON2 pL _ pM _ = withArray "(a, b, c, d, e, f, g, h, i, j, k, l, m)" $ \t -> + liftParseJSON2 _ pL _ _ pM _ = withArray "(a, b, c, d, e, f, g, h, i, j, k, l, m)" $ \t -> let n = V.length t in if n == 13 then (,,,,,,,,,,,,) @@ -2682,14 +2820,14 @@ instance (FromJSON a, FromJSON b, FromJSON c, FromJSON d, FromJSON e, FromJSON f else fail $ "cannot unpack array of length " ++ show n ++ " into a tuple of length 13" instance (FromJSON a, FromJSON b, FromJSON c, FromJSON d, FromJSON e, FromJSON f, FromJSON g, FromJSON h, FromJSON i, FromJSON j, FromJSON k, FromJSON l) => FromJSON1 ((,,,,,,,,,,,,) a b c d e f g h i j k l) where - liftParseJSON = liftParseJSON2 parseJSON parseJSONList + liftParseJSON = liftParseJSON2 omittedField parseJSON parseJSONList instance (FromJSON a, FromJSON b, FromJSON c, FromJSON d, FromJSON e, FromJSON f, FromJSON g, FromJSON h, FromJSON i, FromJSON j, FromJSON k, FromJSON l, FromJSON m) => FromJSON (a, b, c, d, e, f, g, h, i, j, k, l, m) where parseJSON = parseJSON2 instance (FromJSON a, FromJSON b, FromJSON c, FromJSON d, FromJSON e, FromJSON f, FromJSON g, FromJSON h, FromJSON i, FromJSON j, FromJSON k, FromJSON l) => FromJSON2 ((,,,,,,,,,,,,,) a b c d e f g h i j k l) where - liftParseJSON2 pM _ pN _ = withArray "(a, b, c, d, e, f, g, h, i, j, k, l, m, n)" $ \t -> + liftParseJSON2 _ pM _ _ pN _ = withArray "(a, b, c, d, e, f, g, h, i, j, k, l, m, n)" $ \t -> let n = V.length t in if n == 14 then (,,,,,,,,,,,,,) @@ -2710,14 +2848,14 @@ instance (FromJSON a, FromJSON b, FromJSON c, FromJSON d, FromJSON e, FromJSON f else fail $ "cannot unpack array of length " ++ show n ++ " into a tuple of length 14" instance (FromJSON a, FromJSON b, FromJSON c, FromJSON d, FromJSON e, FromJSON f, FromJSON g, FromJSON h, FromJSON i, FromJSON j, FromJSON k, FromJSON l, FromJSON m) => FromJSON1 ((,,,,,,,,,,,,,) a b c d e f g h i j k l m) where - liftParseJSON = liftParseJSON2 parseJSON parseJSONList + liftParseJSON = liftParseJSON2 omittedField parseJSON parseJSONList instance (FromJSON a, FromJSON b, FromJSON c, FromJSON d, FromJSON e, FromJSON f, FromJSON g, FromJSON h, FromJSON i, FromJSON j, FromJSON k, FromJSON l, FromJSON m, FromJSON n) => FromJSON (a, b, c, d, e, f, g, h, i, j, k, l, m, n) where parseJSON = parseJSON2 instance (FromJSON a, FromJSON b, FromJSON c, FromJSON d, FromJSON e, FromJSON f, FromJSON g, FromJSON h, FromJSON i, FromJSON j, FromJSON k, FromJSON l, FromJSON m) => FromJSON2 ((,,,,,,,,,,,,,,) a b c d e f g h i j k l m) where - liftParseJSON2 pN _ pO _ = withArray "(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o)" $ \t -> + liftParseJSON2 _ pN _ _ pO _ = withArray "(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o)" $ \t -> let n = V.length t in if n == 15 then (,,,,,,,,,,,,,,) @@ -2739,7 +2877,7 @@ instance (FromJSON a, FromJSON b, FromJSON c, FromJSON d, FromJSON e, FromJSON f else fail $ "cannot unpack array of length " ++ show n ++ " into a tuple of length 15" instance (FromJSON a, FromJSON b, FromJSON c, FromJSON d, FromJSON e, FromJSON f, FromJSON g, FromJSON h, FromJSON i, FromJSON j, FromJSON k, FromJSON l, FromJSON m, FromJSON n) => FromJSON1 ((,,,,,,,,,,,,,,) a b c d e f g h i j k l m n) where - liftParseJSON = liftParseJSON2 parseJSON parseJSONList + liftParseJSON = liftParseJSON2 omittedField parseJSON parseJSONList instance (FromJSON a, FromJSON b, FromJSON c, FromJSON d, FromJSON e, FromJSON f, FromJSON g, FromJSON h, FromJSON i, FromJSON j, FromJSON k, FromJSON l, FromJSON m, FromJSON n, FromJSON o) => FromJSON (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o) where parseJSON = parseJSON2 diff --git a/src/Data/Aeson/Types/Internal.hs b/src/Data/Aeson/Types/Internal.hs index bdc74750e..758f5fc4b 100644 --- a/src/Data/Aeson/Types/Internal.hs +++ b/src/Data/Aeson/Types/Internal.hs @@ -57,6 +57,7 @@ module Data.Aeson.Types.Internal , constructorTagModifier , allNullaryToStringTag , omitNothingFields + , allowOmittedFields , sumEncoding , unwrapUnaryRecords , tagSingleConstructors @@ -718,50 +719,16 @@ data Options = Options -- omitted from the resulting object. If 'False', the resulting -- object will include those fields mapping to @null@. -- - -- Note that this /does not/ affect parsing: 'Maybe' fields are - -- optional regardless of the value of 'omitNothingFields', subject - -- to the note below. - -- - -- === Note - -- - -- Setting 'omitNothingFields' to 'True' only affects fields which are of - -- type 'Maybe' /uniformly/ in the 'ToJSON' instance. - -- In particular, if the type of a field is declared as a type variable, it - -- will not be omitted from the JSON object, unless the field is - -- specialized upfront in the instance. - -- - -- The same holds for 'Maybe' fields being optional in the 'FromJSON' instance. - -- - -- ==== __Example__ - -- - -- The generic instance for the following type @Fruit@ depends on whether - -- the instance head is @Fruit a@ or @Fruit (Maybe a)@. - -- - -- @ - -- data Fruit a = Fruit - -- { apples :: a -- A field whose type is a type variable. - -- , oranges :: 'Maybe' Int - -- } deriving 'Generic' - -- - -- -- apples required, oranges optional - -- -- Even if 'Data.Aeson.fromJSON' is then specialized to (Fruit ('Maybe' a)). - -- instance 'Data.Aeson.FromJSON' a => 'Data.Aeson.FromJSON' (Fruit a) - -- - -- -- apples optional, oranges optional - -- -- In this instance, the field apples is uniformly of type ('Maybe' a). - -- instance 'Data.Aeson.FromJSON' a => 'Data.Aeson.FromJSON' (Fruit ('Maybe' a)) - -- - -- options :: 'Options' - -- options = 'defaultOptions' { 'omitNothingFields' = 'True' } + -- In @aeson-2.2@ this flag is generalised to omit all values with @'Data.Aeson.Types.omitField' x = True@. + -- If 'False', the resulting object will include those fields encoded as specified. -- - -- -- apples always present in the output, oranges is omitted if 'Nothing' - -- instance 'Data.Aeson.ToJSON' a => 'Data.Aeson.ToJSON' (Fruit a) where - -- 'Data.Aeson.toJSON' = 'Data.Aeson.genericToJSON' options - -- - -- -- both apples and oranges are omitted if 'Nothing' - -- instance 'Data.Aeson.ToJSON' a => 'Data.Aeson.ToJSON' (Fruit ('Maybe' a)) where - -- 'Data.Aeson.toJSON' = 'Data.Aeson.genericToJSON' options - -- @ + -- Note that this /does not/ affect parsing: 'Maybe' fields are + -- optional regardless of the value of 'omitNothingFields'. + -- 'allowOmittedFieds' controls parsing behavior. + , allowOmittedFields :: Bool + -- ^ If 'True', missing fields of a record will be filled + -- with 'omittedField' values (if they are 'Just'). + -- If 'False', all fields will required to present in the record object. , sumEncoding :: SumEncoding -- ^ Specifies how to encode constructors of a sum datatype. , unwrapUnaryRecords :: Bool @@ -777,13 +744,14 @@ data Options = Options } instance Show Options where - show (Options f c a o s u t r) = + show (Options f c a o q s u t r) = "Options {" ++ intercalate ", " [ "fieldLabelModifier =~ " ++ show (f "exampleField") , "constructorTagModifier =~ " ++ show (c "ExampleConstructor") , "allNullaryToStringTag = " ++ show a , "omitNothingFields = " ++ show o + , "allowOmittedFields = " ++ show q , "sumEncoding = " ++ show s , "unwrapUnaryRecords = " ++ show u , "tagSingleConstructors = " ++ show t @@ -866,6 +834,7 @@ data JSONKeyOptions = JSONKeyOptions -- , 'constructorTagModifier' = id -- , 'allNullaryToStringTag' = True -- , 'omitNothingFields' = False +-- , 'allowOmittedFields' = True -- , 'sumEncoding' = 'defaultTaggedObject' -- , 'unwrapUnaryRecords' = False -- , 'tagSingleConstructors' = False @@ -878,6 +847,7 @@ defaultOptions = Options , constructorTagModifier = id , allNullaryToStringTag = True , omitNothingFields = False + , allowOmittedFields = True , sumEncoding = defaultTaggedObject , unwrapUnaryRecords = False , tagSingleConstructors = False diff --git a/src/Data/Aeson/Types/ToJSON.hs b/src/Data/Aeson/Types/ToJSON.hs index 1547079e1..b30b57a2e 100644 --- a/src/Data/Aeson/Types/ToJSON.hs +++ b/src/Data/Aeson/Types/ToJSON.hs @@ -8,11 +8,11 @@ {-# LANGUAGE InstanceSigs #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE PatternGuards #-} {-# LANGUAGE PolyKinds #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeOperators #-} +{-# LANGUAGE TypeApplications #-} {-# LANGUAGE UndecidableInstances #-} module Data.Aeson.Types.ToJSON @@ -23,9 +23,11 @@ module Data.Aeson.Types.ToJSON , ToJSON1(..) , toJSON1 , toEncoding1 + , omitField1 , ToJSON2(..) , toJSON2 , toEncoding2 + , omitField2 -- * Generic JSON classes , GToJSON'(..) , ToArgs(..) @@ -45,6 +47,7 @@ module Data.Aeson.Types.ToJSON -- * Object key-value pairs , KeyValue(..) + , KeyValueOmit(..) , KeyValuePair(..) , FromPairs(..) -- * Functions needed for documentation @@ -75,6 +78,7 @@ import Data.Functor.Sum (Sum(..)) import Data.Functor.These (These1 (..)) import Data.List (intersperse) import Data.List.NonEmpty (NonEmpty(..)) +import Data.Maybe (isNothing) import Data.Ratio (Ratio, denominator, numerator) import Data.Tagged (Tagged(..)) import Data.These (These (..)) @@ -131,7 +135,7 @@ import qualified Data.Primitive.Types as PM import qualified Data.Primitive.PrimArray as PM toJSONPair :: (a -> Value) -> (b -> Value) -> (a, b) -> Value -toJSONPair a b = liftToJSON2 a (listValue a) b (listValue b) +toJSONPair a b = liftToJSON2 (const False) a (listValue a) (const False) b (listValue b) realFloatToJSON :: RealFloat a => a -> Value realFloatToJSON d @@ -156,12 +160,12 @@ class GToJSON' enc arity f where -- and 'liftToEncoding' (if the @arity@ is 'One'). gToJSON :: Options -> ToArgs enc arity a -> f a -> enc --- | A 'ToArgs' value either stores nothing (for 'ToJSON') or it stores the two +-- | A 'ToArgs' value either stores nothing (for 'ToJSON') or it stores the three -- function arguments that encode occurrences of the type parameter (for -- 'ToJSON1'). data ToArgs res arity a where NoToArgs :: ToArgs res Zero a - To1Args :: (a -> res) -> ([a] -> res) -> ToArgs res One a + To1Args :: (a -> Bool) -> (a -> res) -> ([a] -> res) -> ToArgs res One a -- | A configurable generic JSON creator. This function applied to -- 'defaultOptions' is used as the default for 'toJSON' when the type @@ -174,9 +178,9 @@ genericToJSON opts = gToJSON opts NoToArgs . from -- 'defaultOptions' is used as the default for 'liftToJSON' when the type -- is an instance of 'Generic1'. genericLiftToJSON :: (Generic1 f, GToJSON' Value One (Rep1 f)) - => Options -> (a -> Value) -> ([a] -> Value) + => Options -> (a -> Bool) -> (a -> Value) -> ([a] -> Value) -> f a -> Value -genericLiftToJSON opts tj tjl = gToJSON opts (To1Args tj tjl) . from1 +genericLiftToJSON opts o tj tjl = gToJSON opts (To1Args o tj tjl) . from1 -- | A configurable generic JSON encoder. This function applied to -- 'defaultOptions' is used as the default for 'toEncoding' when the type @@ -189,9 +193,9 @@ genericToEncoding opts = gToJSON opts NoToArgs . from -- 'defaultOptions' is used as the default for 'liftToEncoding' when the type -- is an instance of 'Generic1'. genericLiftToEncoding :: (Generic1 f, GToJSON' Encoding One (Rep1 f)) - => Options -> (a -> Encoding) -> ([a] -> Encoding) + => Options -> (a -> Bool) -> (a -> Encoding) -> ([a] -> Encoding) -> f a -> Encoding -genericLiftToEncoding opts te tel = gToJSON opts (To1Args te tel) . from1 +genericLiftToEncoding opts o te tel = gToJSON opts (To1Args o te tel) . from1 ------------------------------------------------------------------------------- -- Class @@ -303,7 +307,6 @@ class ToJSON a where -- instance 'ToJSON' Coord where -- 'toEncoding' = 'genericToEncoding' 'defaultOptions' -- @ - toEncoding :: a -> Encoding toEncoding = E.value . toJSON @@ -313,6 +316,14 @@ class ToJSON a where toEncodingList :: [a] -> Encoding toEncodingList = listEncoding toEncoding + -- | Defines when it is acceptable to omit a field of this type from a record. + -- Used by @('.?=')@ operator, and Generics and TH deriving + -- with @'omitNothingFields' = True@. + -- + -- @since 2.2.0.0 + omitField :: a -> Bool + omitField = const False + -- | @since 2.1.0.0 instance (Generic a, GToJSON' Value Zero (Rep a), GToJSON' Encoding Zero (Rep a)) => ToJSON (Generically a) where toJSON = coerce (genericToJSON defaultOptions :: a -> Value) @@ -323,24 +334,61 @@ instance (Generic a, GToJSON' Value Zero (Rep a), GToJSON' Encoding Zero (Rep a) ------------------------------------------------------------------------------- -- | A key-value pair for encoding a JSON object. -class KeyValue kv where +class KeyValue e kv | kv -> e where + (.=) :: ToJSON v => Key -> v -> kv infixr 8 .= -instance KeyValue Series where - name .= value = E.pair name (toEncoding value) + -- | @since 2.2.0.0 + explicitToField :: (v -> e) -> Key -> v -> kv + +instance KeyValue Encoding Series where + (.=) = explicitToField toEncoding {-# INLINE (.=) #-} -instance (key ~ Key, value ~ Value) => KeyValue (key, value) where - name .= value = (name, toJSON value) + explicitToField f name value = E.pair name (f value) + {-# INLINE explicitToField #-} + +instance (key ~ Key, value ~ Value) => KeyValue Value (key, value) where + (.=) = explicitToField toJSON {-# INLINE (.=) #-} + explicitToField f name value = (name, f value) + {-# INLINE explicitToField #-} + -- | Constructs a singleton 'KM.KeyMap'. For calling functions that -- demand an 'Object' for constructing objects. To be used in -- conjunction with 'mconcat'. Prefer to use 'object' where possible. -instance value ~ Value => KeyValue (KM.KeyMap value) where - name .= value = KM.singleton name (toJSON value) +instance value ~ Value => KeyValue Value (KM.KeyMap value) where + (.=) = explicitToField toJSON {-# INLINE (.=) #-} + + explicitToField f name value = KM.singleton name (f value) + {-# INLINE explicitToField #-} + +-- | An optional key-value pair for envoding to a JSON object +-- +-- @since 2.2.0.0 +-- +class KeyValue e kv => KeyValueOmit e kv | kv -> e where + (.?=) :: ToJSON v => Key -> v -> kv + infixr 8 .?= + + explicitToFieldOmit :: (v -> Bool) -> (v -> e) -> Key -> v -> kv + +instance KeyValueOmit Encoding Series where + name .?= value = if omitField value then mempty else name .= value + {-# INLINE (.?=) #-} + + explicitToFieldOmit o f name value = if o value then mempty else explicitToField f name value + {-# INLINE explicitToFieldOmit #-} + +instance value ~ Value => KeyValueOmit Value (KM.KeyMap value) where + name .?= value = if omitField value then KM.empty else name .= value + {-# INLINE (.?=) #-} + + explicitToFieldOmit o f name value = if o value then KM.empty else explicitToField f name value + {-# INLINE explicitToFieldOmit #-} ------------------------------------------------------------------------------- -- Classes and types for map keys @@ -593,43 +641,50 @@ instance GetConName f => GToJSONKey f -- -- See also 'ToJSON'. class ToJSON1 f where - liftToJSON :: (a -> Value) -> ([a] -> Value) -> f a -> Value + liftToJSON :: (a -> Bool) -> (a -> Value) -> ([a] -> Value) -> f a -> Value default liftToJSON :: (Generic1 f, GToJSON' Value One (Rep1 f)) - => (a -> Value) -> ([a] -> Value) -> f a -> Value + => (a -> Bool) -> (a -> Value) -> ([a] -> Value) -> f a -> Value liftToJSON = genericLiftToJSON defaultOptions - liftToJSONList :: (a -> Value) -> ([a] -> Value) -> [f a] -> Value - liftToJSONList f g = listValue (liftToJSON f g) + liftToJSONList :: (a -> Bool) -> (a -> Value) -> ([a] -> Value) -> [f a] -> Value + liftToJSONList o f g = listValue (liftToJSON o f g) - liftToEncoding :: (a -> Encoding) -> ([a] -> Encoding) -> f a -> Encoding + liftToEncoding :: (a -> Bool) -> (a -> Encoding) -> ([a] -> Encoding) -> f a -> Encoding default liftToEncoding :: (Generic1 f, GToJSON' Encoding One (Rep1 f)) - => (a -> Encoding) -> ([a] -> Encoding) + => (a -> Bool) -> (a -> Encoding) -> ([a] -> Encoding) -> f a -> Encoding liftToEncoding = genericLiftToEncoding defaultOptions - liftToEncodingList :: (a -> Encoding) -> ([a] -> Encoding) -> [f a] -> Encoding - liftToEncodingList f g = listEncoding (liftToEncoding f g) + liftToEncodingList :: (a -> Bool) -> (a -> Encoding) -> ([a] -> Encoding) -> [f a] -> Encoding + liftToEncodingList o f g = listEncoding (liftToEncoding o f g) + + -- | @since 2.2.0.0 + liftOmitField :: (a -> Bool) -> f a -> Bool + liftOmitField _ _ = False -- | @since 2.1.0.0 instance (Generic1 f, GToJSON' Value One (Rep1 f), GToJSON' Encoding One (Rep1 f)) => ToJSON1 (Generically1 f) where - liftToJSON :: forall a. (a -> Value) -> ([a] -> Value) -> Generically1 f a -> Value - liftToJSON = coerce (genericLiftToJSON defaultOptions :: (a -> Value) -> ([a] -> Value) -> f a -> Value) + liftToJSON :: forall a. (a -> Bool) -> (a -> Value) -> ([a] -> Value) -> Generically1 f a -> Value + liftToJSON = coerce (genericLiftToJSON defaultOptions :: (a -> Bool) -> (a -> Value) -> ([a] -> Value) -> f a -> Value) - liftToEncoding :: forall a. (a -> Encoding) -> ([a] -> Encoding) -> Generically1 f a -> Encoding - liftToEncoding = coerce (genericLiftToEncoding defaultOptions :: (a -> Encoding) -> ([a] -> Encoding) -> f a -> Encoding) + liftToEncoding :: forall a. (a -> Bool) -> (a -> Encoding) -> ([a] -> Encoding) -> Generically1 f a -> Encoding + liftToEncoding = coerce (genericLiftToEncoding defaultOptions :: (a -> Bool) -> (a -> Encoding) -> ([a] -> Encoding) -> f a -> Encoding) -- | Lift the standard 'toJSON' function through the type constructor. toJSON1 :: (ToJSON1 f, ToJSON a) => f a -> Value -toJSON1 = liftToJSON toJSON toJSONList +toJSON1 = liftToJSON omitField toJSON toJSONList {-# INLINE toJSON1 #-} -- | Lift the standard 'toEncoding' function through the type constructor. toEncoding1 :: (ToJSON1 f, ToJSON a) => f a -> Encoding -toEncoding1 = liftToEncoding toEncoding toEncodingList +toEncoding1 = liftToEncoding omitField toEncoding toEncodingList {-# INLINE toEncoding1 #-} +omitField1 :: (ToJSON1 f, ToJSON a) => f a -> Bool +omitField1 = liftOmitField omitField + -- | Lifting of the 'ToJSON' class to binary type constructors. -- -- Instead of manually writing your 'ToJSON2' instance, "Data.Aeson.TH" @@ -638,24 +693,32 @@ toEncoding1 = liftToEncoding toEncoding toEncodingList -- The compiler cannot provide a default generic implementation for 'liftToJSON2', -- unlike 'toJSON' and 'liftToJSON'. class ToJSON2 f where - liftToJSON2 :: (a -> Value) -> ([a] -> Value) -> (b -> Value) -> ([b] -> Value) -> f a b -> Value - liftToJSONList2 :: (a -> Value) -> ([a] -> Value) -> (b -> Value) -> ([b] -> Value) -> [f a b] -> Value - liftToJSONList2 fa ga fb gb = listValue (liftToJSON2 fa ga fb gb) + liftToJSON2 :: (a -> Bool) -> (a -> Value) -> ([a] -> Value) -> (b -> Bool) -> (b -> Value) -> ([b] -> Value) -> f a b -> Value + liftToJSONList2 :: (a -> Bool) -> (a -> Value) -> ([a] -> Value) -> (b -> Bool) -> (b -> Value) -> ([b] -> Value) -> [f a b] -> Value + liftToJSONList2 oa fa ga ob fb gb = listValue (liftToJSON2 oa fa ga ob fb gb) + + liftToEncoding2 :: (a -> Bool) -> (a -> Encoding) -> ([a] -> Encoding) -> (b -> Bool) -> (b -> Encoding) -> ([b] -> Encoding) -> f a b -> Encoding + liftToEncodingList2 :: (a -> Bool) -> (a -> Encoding) -> ([a] -> Encoding) -> (b -> Bool) -> (b -> Encoding) -> ([b] -> Encoding) -> [f a b] -> Encoding + liftToEncodingList2 oa fa ga ob fb gb = listEncoding (liftToEncoding2 oa fa ga ob fb gb) - liftToEncoding2 :: (a -> Encoding) -> ([a] -> Encoding) -> (b -> Encoding) -> ([b] -> Encoding) -> f a b -> Encoding - liftToEncodingList2 :: (a -> Encoding) -> ([a] -> Encoding) -> (b -> Encoding) -> ([b] -> Encoding) -> [f a b] -> Encoding - liftToEncodingList2 fa ga fb gb = listEncoding (liftToEncoding2 fa ga fb gb) + -- | @since 2.2.0.0 + liftOmitField2 :: (a -> Bool) -> (b -> Bool) -> f a b -> Bool + liftOmitField2 _ _ _ = False -- | Lift the standard 'toJSON' function through the type constructor. toJSON2 :: (ToJSON2 f, ToJSON a, ToJSON b) => f a b -> Value -toJSON2 = liftToJSON2 toJSON toJSONList toJSON toJSONList +toJSON2 = liftToJSON2 omitField toJSON toJSONList omitField toJSON toJSONList {-# INLINE toJSON2 #-} -- | Lift the standard 'toEncoding' function through the type constructor. toEncoding2 :: (ToJSON2 f, ToJSON a, ToJSON b) => f a b -> Encoding -toEncoding2 = liftToEncoding2 toEncoding toEncodingList toEncoding toEncodingList +toEncoding2 = liftToEncoding2 omitField toEncoding toEncodingList omitField toEncoding toEncodingList {-# INLINE toEncoding2 #-} +omitField2 :: (ToJSON2 f, ToJSON a, ToJSON b) => f a b -> Bool +omitField2 = liftOmitField2 omitField omitField +{-# INLINE omitField2 #-} + ------------------------------------------------------------------------------- -- Encoding functions ------------------------------------------------------------------------------- @@ -690,9 +753,9 @@ listValue f = Array . V.fromList . map f -- These are needed for key-class default definitions instance ToJSON1 [] where - liftToJSON _ to' = to' + liftToJSON _ _ to' = to' - liftToEncoding _ to' = to' + liftToEncoding _ _ to' = to' instance (ToJSON a) => ToJSON [a] where {-# SPECIALIZE instance ToJSON String #-} @@ -716,7 +779,7 @@ instance {-# OVERLAPPABLE #-} (GToJSON' enc arity a) => GToJSON' enc arity (M1 i instance GToJSON' enc One Par1 where -- Direct occurrences of the last type parameter are encoded with the -- function passed in as an argument: - gToJSON _opts (To1Args tj _) = tj . unPar1 + gToJSON _opts (To1Args _ tj _) = tj . unPar1 -- TODO {-# INLINE gToJSON #-} instance ( ConsToJSON enc arity a @@ -770,7 +833,7 @@ instance ToJSON a => GToJSON' Value arity (K1 i a) where instance ToJSON1 f => GToJSON' Value One (Rec1 f) where -- Recursive occurrences of the last type parameter are encoded using their -- ToJSON1 instance: - gToJSON _opts (To1Args tj tjl) = liftToJSON tj tjl . unRec1 + gToJSON _opts (To1Args o tj tjl) = liftToJSON o tj tjl . unRec1 {-# INLINE gToJSON #-} instance GToJSON' Value arity U1 where @@ -804,7 +867,7 @@ instance ( ToJSON1 f -- instance to generically encode the innermost type: gToJSON opts targs = let gtj = gToJSON opts targs in - liftToJSON gtj (listValue gtj) . unComp1 + liftToJSON (const False) gtj (listValue gtj) . unComp1 {-# INLINE gToJSON #-} -------------------------------------------------------------------------------- @@ -824,7 +887,7 @@ instance ToJSON a => GToJSON' Encoding arity (K1 i a) where instance ToJSON1 f => GToJSON' Encoding One (Rec1 f) where -- Recursive occurrences of the last type parameter are encoded using their -- ToEncoding1 instance: - gToJSON _opts (To1Args te tel) = liftToEncoding te tel . unRec1 + gToJSON _opts (To1Args o te tel) = liftToEncoding o te tel . unRec1 {-# INLINE gToJSON #-} instance GToJSON' Encoding arity U1 where @@ -851,7 +914,7 @@ instance ( ToJSON1 f -- instance to generically encode the innermost type: gToJSON opts targs = let gte = gToJSON opts targs in - liftToEncoding gte (listEncoding gte) . unComp1 + liftToEncoding (const False) gte (listEncoding gte) . unComp1 {-# INLINE gToJSON #-} -------------------------------------------------------------------------------- @@ -1102,50 +1165,54 @@ instance ( Monoid pairs {-# INLINE recordToPairs #-} instance ( Selector s - , GToJSON' enc arity a + , GToJSON' enc arity (K1 i t) , KeyValuePair enc pairs - ) => RecordToPairs enc pairs arity (S1 s a) + , ToJSON t + ) => RecordToPairs enc pairs arity (S1 s (K1 i t)) where - recordToPairs = fieldToPair + recordToPairs opts targs m1 + | omitNothingFields opts + , omitField (unK1 $ unM1 m1 :: t) + = mempty + + | otherwise = + let key = Key.fromString $ fieldLabelModifier opts (selName m1) + value = gToJSON opts targs (unM1 m1) + in key `pair` value {-# INLINE recordToPairs #-} -instance {-# INCOHERENT #-} - ( Selector s - , GToJSON' enc arity (K1 i (Maybe a)) - , KeyValuePair enc pairs - , Monoid pairs - ) => RecordToPairs enc pairs arity (S1 s (K1 i (Maybe a))) +instance ( Selector s + , GToJSON' enc One (Rec1 f) + , KeyValuePair enc pairs + , ToJSON1 f + ) => RecordToPairs enc pairs One (S1 s (Rec1 f)) where - recordToPairs opts _ (M1 k1) | omitNothingFields opts - , K1 Nothing <- k1 = mempty - recordToPairs opts targs m1 = fieldToPair opts targs m1 + recordToPairs opts targs@(To1Args o _ _) m1 + | omitNothingFields opts + , liftOmitField o $ unRec1 $ unM1 m1 + = mempty + + | otherwise = + let key = Key.fromString $ fieldLabelModifier opts (selName m1) + value = gToJSON opts targs (unM1 m1) + in key `pair` value {-# INLINE recordToPairs #-} -#if !MIN_VERSION_base(4,16,0) -instance {-# INCOHERENT #-} - ( Selector s - , GToJSON' enc arity (K1 i (Maybe a)) - , KeyValuePair enc pairs - , Monoid pairs - ) => RecordToPairs enc pairs arity (S1 s (K1 i (Semigroup.Option a))) +instance ( Selector s + , GToJSON' enc One Par1 + , KeyValuePair enc pairs + ) => RecordToPairs enc pairs One (S1 s Par1) where - recordToPairs opts targs = recordToPairs opts targs . unwrap - where - unwrap :: S1 s (K1 i (Semigroup.Option a)) p -> S1 s (K1 i (Maybe a)) p - unwrap (M1 (K1 (Semigroup.Option a))) = M1 (K1 a) + recordToPairs opts targs@(To1Args o _ _) m1 + | omitNothingFields opts + , o (unPar1 (unM1 m1)) + = mempty + + | otherwise = + let key = Key.fromString $ fieldLabelModifier opts (selName m1) + value = gToJSON opts targs (unM1 m1) + in key `pair` value {-# INLINE recordToPairs #-} -#endif - -fieldToPair :: (Selector s - , GToJSON' enc arity a - , KeyValuePair enc pairs) - => Options -> ToArgs enc arity p - -> S1 s a p -> pairs -fieldToPair opts targs m1 = - let key = Key.fromString $ fieldLabelModifier opts (selName m1) - value = gToJSON opts targs (unM1 m1) - in key `pair` value -{-# INLINE fieldToPair #-} -------------------------------------------------------------------------------- @@ -1240,43 +1307,49 @@ instance {-# OVERLAPPING #-} ------------------------------------------------------------------------------- instance ToJSON2 Const where - liftToJSON2 t _ _ _ (Const x) = t x - liftToEncoding2 t _ _ _ (Const x) = t x + liftToJSON2 _ t _ _ _ _ (Const x) = t x + liftToEncoding2 _ t _ _ _ _ (Const x) = t x + liftOmitField2 o _ (Const x) = o x instance ToJSON a => ToJSON1 (Const a) where - liftToJSON _ _ (Const x) = toJSON x - liftToEncoding _ _ (Const x) = toEncoding x + liftToJSON _ _ _ (Const x) = toJSON x + liftToEncoding _ _ _ (Const x) = toEncoding x + liftOmitField _ (Const x) = omitField x instance ToJSON a => ToJSON (Const a b) where toJSON (Const x) = toJSON x toEncoding (Const x) = toEncoding x + omitField (Const x) = omitField x instance (ToJSON a, ToJSONKey a) => ToJSONKey (Const a b) where toJSONKey = contramap getConst toJSONKey instance ToJSON1 Maybe where - liftToJSON t _ (Just a) = t a - liftToJSON _ _ Nothing = Null + liftToJSON _ t _ (Just a) = t a + liftToJSON _ _ _ Nothing = Null - liftToEncoding t _ (Just a) = t a - liftToEncoding _ _ Nothing = E.null_ + liftToEncoding _ t _ (Just a) = t a + liftToEncoding _ _ _ Nothing = E.null_ + + liftOmitField _ = isNothing instance (ToJSON a) => ToJSON (Maybe a) where toJSON = toJSON1 + omitField = omitField1 toEncoding = toEncoding1 instance ToJSON2 Either where - liftToJSON2 toA _ _toB _ (Left a) = Object $ KM.singleton "Left" (toA a) - liftToJSON2 _toA _ toB _ (Right b) = Object $ KM.singleton "Right" (toB b) + liftToJSON2 _ toA _ _ _toB _ (Left a) = Object $ KM.singleton "Left" (toA a) + liftToJSON2 _ _toA _ _ toB _ (Right b) = Object $ KM.singleton "Right" (toB b) - liftToEncoding2 toA _ _toB _ (Left a) = E.pairs $ E.pair "Left" $ toA a - liftToEncoding2 _toA _ toB _ (Right b) = E.pairs $ E.pair "Right" $ toB b + liftToEncoding2 _ toA _ _ _toB _ (Left a) = E.pairs $ E.pair "Left" $ toA a + liftToEncoding2 _ _toA _ _ toB _ (Right b) = E.pairs $ E.pair "Right" $ toB b instance (ToJSON a) => ToJSON1 (Either a) where - liftToJSON = liftToJSON2 toJSON toJSONList - liftToEncoding = liftToEncoding2 toEncoding toEncodingList + liftToJSON = liftToJSON2 omitField toJSON toJSONList + liftToEncoding = liftToEncoding2 omitField toEncoding toEncodingList instance (ToJSON a, ToJSON b) => ToJSON (Either a b) where toJSON = toJSON2 @@ -1311,6 +1384,7 @@ orderingToText o = case o of instance ToJSON () where toJSON _ = emptyArray toEncoding _ = emptyArray_ + omitField _ = True instance ToJSON Char where @@ -1487,8 +1561,8 @@ instance ToJSONKey Version where ------------------------------------------------------------------------------- instance ToJSON1 NonEmpty where - liftToJSON t _ = listValue t . NE.toList - liftToEncoding t _ = listEncoding t . NE.toList + liftToJSON _ t _ = listValue t . NE.toList + liftToEncoding _ t _ = listEncoding t . NE.toList instance (ToJSON a) => ToJSON (NonEmpty a) where toJSON = toJSON1 @@ -1510,8 +1584,8 @@ instance ToJSONKey Scientific where ------------------------------------------------------------------------------- instance ToJSON1 DList.DList where - liftToJSON t _ = listValue t . toList - liftToEncoding t _ = listEncoding t . toList + liftToJSON _ t _ = listValue t . toList + liftToEncoding _ t _ = listEncoding t . toList instance (ToJSON a) => ToJSON (DList.DList a) where toJSON = toJSON1 @@ -1519,8 +1593,8 @@ instance (ToJSON a) => ToJSON (DList.DList a) where -- | @since 1.5.3.0 instance ToJSON1 DNE.DNonEmpty where - liftToJSON t _ = listValue t . DNE.toList - liftToEncoding t _ = listEncoding t . DNE.toList + liftToJSON _ t _ = listValue t . DNE.toList + liftToEncoding _ t _ = listEncoding t . DNE.toList -- | @since 1.5.3.0 instance (ToJSON a) => ToJSON (DNE.DNonEmpty a) where @@ -1533,19 +1607,19 @@ instance (ToJSON a) => ToJSON (DNE.DNonEmpty a) where -- | @since 2.0.2.0 instance ToJSON1 Solo where - liftToJSON t _ (Solo a) = t a - liftToJSONList _ tl xs = tl (map getSolo xs) + liftToJSON _ t _ (Solo a) = t a + liftToJSONList _ _ tl xs = tl (map getSolo xs) - liftToEncoding t _ (Solo a) = t a - liftToEncodingList _ tl xs = tl (map getSolo xs) + liftToEncoding _ t _ (Solo a) = t a + liftToEncodingList _ _ tl xs = tl (map getSolo xs) -- | @since 2.0.2.0 instance (ToJSON a) => ToJSON (Solo a) where toJSON = toJSON1 - toJSONList = liftToJSONList toJSON toJSONList + toJSONList = liftToJSONList omitField toJSON toJSONList toEncoding = toEncoding1 - toEncodingList = liftToEncodingList toEncoding toEncodingList + toEncodingList = liftToEncodingList omitField toEncoding toEncodingList -- | @since 2.0.2.0 instance (ToJSONKey a) => ToJSONKey (Solo a) where @@ -1557,18 +1631,22 @@ instance (ToJSONKey a) => ToJSONKey (Solo a) where ------------------------------------------------------------------------------- instance ToJSON1 Identity where - liftToJSON t _ (Identity a) = t a - liftToJSONList _ tl xs = tl (map runIdentity xs) + liftToJSON _ t _ (Identity a) = t a + liftToJSONList _ _ tl xs = tl (map runIdentity xs) + + liftToEncoding _ t _ (Identity a) = t a + liftToEncodingList _ _ tl xs = tl (map runIdentity xs) - liftToEncoding t _ (Identity a) = t a - liftToEncodingList _ tl xs = tl (map runIdentity xs) + liftOmitField o (Identity a) = o a instance (ToJSON a) => ToJSON (Identity a) where toJSON = toJSON1 - toJSONList = liftToJSONList toJSON toJSONList + toJSONList = liftToJSONList omitField toJSON toJSONList toEncoding = toEncoding1 - toEncodingList = liftToEncodingList toEncoding toEncodingList + toEncodingList = liftToEncodingList omitField toEncoding toEncodingList + + omitField (Identity x) = omitField x instance (ToJSONKey a) => ToJSONKey (Identity a) where toJSONKey = contramapToJSONKeyFunction runIdentity toJSONKey @@ -1576,58 +1654,60 @@ instance (ToJSONKey a) => ToJSONKey (Identity a) where instance (ToJSON1 f, ToJSON1 g) => ToJSON1 (Compose f g) where - liftToJSON tv tvl (Compose x) = liftToJSON g gl x + liftToJSON o tv tvl (Compose x) = liftToJSON (liftOmitField o) g gl x where - g = liftToJSON tv tvl - gl = liftToJSONList tv tvl + g = liftToJSON o tv tvl + gl = liftToJSONList o tv tvl - liftToJSONList te tel xs = liftToJSONList g gl (map getCompose xs) + liftToJSONList o te tel xs = liftToJSONList (liftOmitField o) g gl (map getCompose xs) where - g = liftToJSON te tel - gl = liftToJSONList te tel + g = liftToJSON o te tel + gl = liftToJSONList o te tel - liftToEncoding te tel (Compose x) = liftToEncoding g gl x + liftToEncoding o te tel (Compose x) = liftToEncoding (liftOmitField o) g gl x where - g = liftToEncoding te tel - gl = liftToEncodingList te tel + g = liftToEncoding o te tel + gl = liftToEncodingList o te tel - liftToEncodingList te tel xs = liftToEncodingList g gl (map getCompose xs) + liftToEncodingList o te tel xs = liftToEncodingList (liftOmitField o) g gl (map getCompose xs) where - g = liftToEncoding te tel - gl = liftToEncodingList te tel + g = liftToEncoding o te tel + gl = liftToEncodingList o te tel + + liftOmitField o (Compose xs)= liftOmitField (liftOmitField o) xs instance (ToJSON1 f, ToJSON1 g, ToJSON a) => ToJSON (Compose f g a) where toJSON = toJSON1 - toJSONList = liftToJSONList toJSON toJSONList + toJSONList = liftToJSONList omitField toJSON toJSONList toEncoding = toEncoding1 - toEncodingList = liftToEncodingList toEncoding toEncodingList - + toEncodingList = liftToEncodingList omitField toEncoding toEncodingList + omitField = omitField1 instance (ToJSON1 f, ToJSON1 g) => ToJSON1 (Product f g) where - liftToJSON tv tvl (Pair x y) = liftToJSON2 tx txl ty tyl (x, y) + liftToJSON o tv tvl (Pair x y) = liftToJSON2 (liftOmitField o) tx txl (liftOmitField o) ty tyl (x, y) where - tx = liftToJSON tv tvl - txl = liftToJSONList tv tvl - ty = liftToJSON tv tvl - tyl = liftToJSONList tv tvl + tx = liftToJSON o tv tvl + txl = liftToJSONList o tv tvl + ty = liftToJSON o tv tvl + tyl = liftToJSONList o tv tvl - liftToEncoding te tel (Pair x y) = liftToEncoding2 tx txl ty tyl (x, y) + liftToEncoding o te tel (Pair x y) = liftToEncoding2 (liftOmitField o) tx txl (liftOmitField o) ty tyl (x, y) where - tx = liftToEncoding te tel - txl = liftToEncodingList te tel - ty = liftToEncoding te tel - tyl = liftToEncodingList te tel + tx = liftToEncoding o te tel + txl = liftToEncodingList o te tel + ty = liftToEncoding o te tel + tyl = liftToEncodingList o te tel instance (ToJSON1 f, ToJSON1 g, ToJSON a) => ToJSON (Product f g a) where toJSON = toJSON1 toEncoding = toEncoding1 instance (ToJSON1 f, ToJSON1 g) => ToJSON1 (Sum f g) where - liftToJSON tv tvl (InL x) = Object $ KM.singleton "InL" (liftToJSON tv tvl x) - liftToJSON tv tvl (InR y) = Object $ KM.singleton "InR" (liftToJSON tv tvl y) + liftToJSON o tv tvl (InL x) = Object $ KM.singleton "InL" (liftToJSON o tv tvl x) + liftToJSON o tv tvl (InR y) = Object $ KM.singleton "InR" (liftToJSON o tv tvl y) - liftToEncoding te tel (InL x) = E.pairs $ E.pair "InL" $ liftToEncoding te tel x - liftToEncoding te tel (InR y) = E.pairs $ E.pair "InR" $ liftToEncoding te tel y + liftToEncoding o te tel (InL x) = E.pairs $ E.pair "InL" $ liftToEncoding o te tel x + liftToEncoding o te tel (InR y) = E.pairs $ E.pair "InR" $ liftToEncoding o te tel y instance (ToJSON1 f, ToJSON1 g, ToJSON a) => ToJSON (Sum f g a) where toJSON = toJSON1 @@ -1638,8 +1718,8 @@ instance (ToJSON1 f, ToJSON1 g, ToJSON a) => ToJSON (Sum f g a) where ------------------------------------------------------------------------------- instance ToJSON1 Seq.Seq where - liftToJSON t _ = listValue t . toList - liftToEncoding t _ = listEncoding t . toList + liftToJSON _ t _ = listValue t . toList + liftToEncoding _ t _ = listEncoding t . toList instance (ToJSON a) => ToJSON (Seq.Seq a) where toJSON = toJSON1 @@ -1647,8 +1727,8 @@ instance (ToJSON a) => ToJSON (Seq.Seq a) where instance ToJSON1 Set.Set where - liftToJSON t _ = listValue t . Set.toList - liftToEncoding t _ = listEncoding t . Set.toList + liftToJSON _ t _ = listValue t . Set.toList + liftToEncoding _ t _ = listEncoding t . Set.toList instance (ToJSON a) => ToJSON (Set.Set a) where toJSON = toJSON1 @@ -1660,15 +1740,15 @@ instance ToJSON IntSet.IntSet where toEncoding = toEncoding . IntSet.toList instance ToJSON1 IntMap.IntMap where - liftToJSON t tol = liftToJSON to' tol' . IntMap.toList + liftToJSON o t tol = liftToJSON (liftOmitField o) to' tol' . IntMap.toList where - to' = liftToJSON2 toJSON toJSONList t tol - tol' = liftToJSONList2 toJSON toJSONList t tol + to' = liftToJSON2 omitField toJSON toJSONList o t tol + tol' = liftToJSONList2 omitField toJSON toJSONList o t tol - liftToEncoding t tol = liftToEncoding to' tol' . IntMap.toList + liftToEncoding o t tol = liftToEncoding (liftOmitField o) to' tol' . IntMap.toList where - to' = liftToEncoding2 toEncoding toEncodingList t tol - tol' = liftToEncodingList2 toEncoding toEncodingList t tol + to' = liftToEncoding2 omitField toEncoding toEncodingList o t tol + tol' = liftToEncodingList2 omitField toEncoding toEncodingList o t tol instance ToJSON a => ToJSON (IntMap.IntMap a) where toJSON = toJSON1 @@ -1676,11 +1756,11 @@ instance ToJSON a => ToJSON (IntMap.IntMap a) where instance ToJSONKey k => ToJSON1 (M.Map k) where - liftToJSON g _ = case toJSONKey of + liftToJSON _ g _ = case toJSONKey of ToJSONKeyText f _ -> Object . KM.fromMap . mapKeyValO f g ToJSONKeyValue f _ -> Array . V.fromList . map (toJSONPair f g) . M.toList - liftToEncoding g _ = case toJSONKey of + liftToEncoding _ g _ = case toJSONKey of ToJSONKeyText _ f -> dict f g M.foldrWithKey ToJSONKeyValue _ f -> listEncoding (pairEncoding f) . M.toList where @@ -1693,21 +1773,21 @@ instance (ToJSON v, ToJSONKey k) => ToJSON (M.Map k v) where instance ToJSON1 Tree.Tree where - liftToJSON t tol = go + liftToJSON o t tol = go where go (Tree.Node root branches) = - liftToJSON2 t tol to' tol' (root, branches) + liftToJSON2 o t tol (const False) to' tol' (root, branches) - to' = liftToJSON go (listValue go) - tol' = liftToJSONList go (listValue go) + to' = liftToJSON (const False) go (listValue go) + tol' = liftToJSONList (const False) go (listValue go) - liftToEncoding t tol = go + liftToEncoding o t tol = go where go (Tree.Node root branches) = - liftToEncoding2 t tol to' tol' (root, branches) + liftToEncoding2 o t tol (const False) to' tol' (root, branches) - to' = liftToEncoding go (listEncoding go) - tol' = liftToEncodingList go (listEncoding go) + to' = liftToEncoding (const False) go (listEncoding go) + tol' = liftToEncodingList (const False) go (listEncoding go) instance (ToJSON v) => ToJSON (Tree.Tree v) where toJSON = toJSON1 @@ -1730,8 +1810,8 @@ instance ToJSONKey UUID.UUID where ------------------------------------------------------------------------------- instance ToJSON1 Vector where - liftToJSON t _ = Array . V.map t - liftToEncoding t _ = listEncoding t . V.toList + liftToJSON _ t _ = Array . V.map t + liftToEncoding _ t _ = listEncoding t . V.toList instance (ToJSON a) => ToJSON (Vector a) where {-# SPECIALIZE instance ToJSON Array #-} @@ -1766,8 +1846,8 @@ instance (VG.Vector VU.Vector a, ToJSON a) => ToJSON (VU.Vector a) where ------------------------------------------------------------------------------- instance ToJSON1 HashSet.HashSet where - liftToJSON t _ = listValue t . HashSet.toList - liftToEncoding t _ = listEncoding t . HashSet.toList + liftToJSON _ t _ = listValue t . HashSet.toList + liftToEncoding _ t _ = listEncoding t . HashSet.toList instance (ToJSON a) => ToJSON (HashSet.HashSet a) where toJSON = toJSON1 @@ -1775,13 +1855,13 @@ instance (ToJSON a) => ToJSON (HashSet.HashSet a) where instance ToJSONKey k => ToJSON1 (H.HashMap k) where - liftToJSON g _ = case toJSONKey of + liftToJSON _ g _ = case toJSONKey of ToJSONKeyText f _ -> Object . KM.fromHashMap . mapKeyVal f g ToJSONKeyValue f _ -> Array . V.fromList . map (toJSONPair f g) . H.toList -- liftToEncoding :: forall a. (a -> Encoding) -> ([a] -> Encoding) -> KM.HashMap k a -> Encoding - liftToEncoding g _ = case toJSONKey of + liftToEncoding _ g _ = case toJSONKey of ToJSONKeyText _ f -> dict f g H.foldrWithKey ToJSONKeyValue _ f -> listEncoding (pairEncoding f) . H.toList where @@ -1796,8 +1876,8 @@ instance (ToJSON v, ToJSONKey k) => ToJSON (H.HashMap k v) where ------------------------------------------------------------------------------- instance ToJSON1 KM.KeyMap where - liftToJSON g _ = Object . fmap g - liftToEncoding g _ = dict E.key g KM.foldrWithKey + liftToJSON _ g _ = Object . fmap g + liftToEncoding _ g _ = dict E.key g KM.foldrWithKey instance (ToJSON v) => ToJSON (KM.KeyMap v) where {-# SPECIALIZE instance ToJSON Object #-} @@ -1993,84 +2073,99 @@ instance ToJSONKey QuarterOfYear where ------------------------------------------------------------------------------- instance ToJSON1 Monoid.Dual where - liftToJSON t _ = t . Monoid.getDual - liftToEncoding t _ = t . Monoid.getDual + liftToJSON _ t _ = t . Monoid.getDual + liftToEncoding _ t _ = t . Monoid.getDual + liftOmitField = coerce instance ToJSON a => ToJSON (Monoid.Dual a) where toJSON = toJSON1 toEncoding = toEncoding1 - + omitField = omitField1 instance ToJSON1 Monoid.First where - liftToJSON t to' = liftToJSON t to' . Monoid.getFirst - liftToEncoding t to' = liftToEncoding t to' . Monoid.getFirst - + liftToJSON o t to' = liftToJSON o t to' . Monoid.getFirst + liftToEncoding o t to' = liftToEncoding o t to' . Monoid.getFirst + liftOmitField :: forall a. (a -> Bool) -> Monoid.First a -> Bool + liftOmitField _ = coerce (isNothing @a) + instance ToJSON a => ToJSON (Monoid.First a) where toJSON = toJSON1 toEncoding = toEncoding1 - + omitField = omitField1 instance ToJSON1 Monoid.Last where - liftToJSON t to' = liftToJSON t to' . Monoid.getLast - liftToEncoding t to' = liftToEncoding t to' . Monoid.getLast + liftToJSON o t to' = liftToJSON o t to' . Monoid.getLast + liftToEncoding o t to' = liftToEncoding o t to' . Monoid.getLast + + liftOmitField :: forall a. (a -> Bool) -> Monoid.Last a -> Bool + liftOmitField _ = coerce (isNothing @a) instance ToJSON a => ToJSON (Monoid.Last a) where toJSON = toJSON1 toEncoding = toEncoding1 - + omitField = omitField1 instance ToJSON1 Semigroup.Min where - liftToJSON t _ (Semigroup.Min x) = t x - liftToEncoding t _ (Semigroup.Min x) = t x + liftToJSON _ t _ (Semigroup.Min x) = t x + liftToEncoding _ t _ (Semigroup.Min x) = t x + liftOmitField = coerce instance ToJSON a => ToJSON (Semigroup.Min a) where toJSON = toJSON1 toEncoding = toEncoding1 + omitField = omitField1 instance ToJSON1 Semigroup.Max where - liftToJSON t _ (Semigroup.Max x) = t x - liftToEncoding t _ (Semigroup.Max x) = t x + liftToJSON _ t _ (Semigroup.Max x) = t x + liftToEncoding _ t _ (Semigroup.Max x) = t x + liftOmitField = coerce instance ToJSON a => ToJSON (Semigroup.Max a) where toJSON = toJSON1 toEncoding = toEncoding1 + omitField = omitField1 instance ToJSON1 Semigroup.First where - liftToJSON t _ (Semigroup.First x) = t x - liftToEncoding t _ (Semigroup.First x) = t x + liftToJSON _ t _ (Semigroup.First x) = t x + liftToEncoding _ t _ (Semigroup.First x) = t x + liftOmitField = coerce instance ToJSON a => ToJSON (Semigroup.First a) where toJSON = toJSON1 toEncoding = toEncoding1 - + omitField = omitField1 instance ToJSON1 Semigroup.Last where - liftToJSON t _ (Semigroup.Last x) = t x - liftToEncoding t _ (Semigroup.Last x) = t x + liftToJSON _ t _ (Semigroup.Last x) = t x + liftToEncoding _ t _ (Semigroup.Last x) = t x + liftOmitField = coerce instance ToJSON a => ToJSON (Semigroup.Last a) where toJSON = toJSON1 toEncoding = toEncoding1 - + omitField = omitField1 instance ToJSON1 Semigroup.WrappedMonoid where - liftToJSON t _ (Semigroup.WrapMonoid x) = t x - liftToEncoding t _ (Semigroup.WrapMonoid x) = t x - + liftToJSON _ t _ (Semigroup.WrapMonoid x) = t x + liftToEncoding _ t _ (Semigroup.WrapMonoid x) = t x + liftOmitField = coerce + instance ToJSON a => ToJSON (Semigroup.WrappedMonoid a) where toJSON = toJSON1 toEncoding = toEncoding1 - + omitField = omitField1 #if !MIN_VERSION_base(4,16,0) instance ToJSON1 Semigroup.Option where - liftToJSON t to' = liftToJSON t to' . Semigroup.getOption - liftToEncoding t to' = liftToEncoding t to' . Semigroup.getOption + liftToJSON o t to' = liftToJSON o t to' . Semigroup.getOption + liftToEncoding o t to' = liftToEncoding o t to' . Semigroup.getOption + liftOmitField _ = isNothing . Semigroup.getOption instance ToJSON a => ToJSON (Semigroup.Option a) where toJSON = toJSON1 toEncoding = toEncoding1 + omitField = omitField1 #endif ------------------------------------------------------------------------------- @@ -2079,18 +2174,19 @@ instance ToJSON a => ToJSON (Semigroup.Option a) where -- | @since 1.5.3.0 instance ToJSON1 f => ToJSON (F.Fix f) where - toJSON = go where go (F.Fix f) = liftToJSON go toJSONList f - toEncoding = go where go (F.Fix f) = liftToEncoding go toEncodingList f + toJSON = go where go (F.Fix f) = liftToJSON omitField go toJSONList f + toEncoding = go where go (F.Fix f) = liftToEncoding omitField go toEncodingList f + omitField = go where go (F.Fix f) = liftOmitField go f -- | @since 1.5.3.0 instance (ToJSON1 f, Functor f) => ToJSON (F.Mu f) where - toJSON = F.foldMu (liftToJSON id (listValue id)) - toEncoding = F.foldMu (liftToEncoding id (listEncoding id)) + toJSON = F.foldMu (liftToJSON (const False) id (listValue id)) + toEncoding = F.foldMu (liftToEncoding (const False) id (listEncoding id)) -- | @since 1.5.3.0 instance (ToJSON1 f, Functor f) => ToJSON (F.Nu f) where - toJSON = F.foldNu (liftToJSON id (listValue id)) - toEncoding = F.foldNu (liftToEncoding id (listEncoding id)) + toJSON = F.foldNu (liftToJSON (const False) id (listValue id)) + toEncoding = F.foldNu (liftToEncoding (const False) id (listEncoding id)) ------------------------------------------------------------------------------- -- strict @@ -2103,13 +2199,13 @@ instance (ToJSON a, ToJSON b) => ToJSON (S.These a b) where -- | @since 1.5.3.0 instance ToJSON2 S.These where - liftToJSON2 toa toas tob tobs = liftToJSON2 toa toas tob tobs . S.toLazy - liftToEncoding2 toa toas tob tobs = liftToEncoding2 toa toas tob tobs . S.toLazy + liftToJSON2 oa toa toas ob tob tobs = liftToJSON2 oa toa toas ob tob tobs . S.toLazy + liftToEncoding2 oa toa toas ob tob tobs = liftToEncoding2 oa toa toas ob tob tobs . S.toLazy -- | @since 1.5.3.0 instance ToJSON a => ToJSON1 (S.These a) where - liftToJSON toa tos = liftToJSON toa tos . S.toLazy - liftToEncoding toa tos = liftToEncoding toa tos . S.toLazy + liftToJSON oa toa tos = liftToJSON oa toa tos . S.toLazy + liftToEncoding oa toa tos = liftToEncoding oa toa tos . S.toLazy -- | @since 1.5.3.0 instance (ToJSON a, ToJSON b) => ToJSON (S.Pair a b) where @@ -2118,13 +2214,13 @@ instance (ToJSON a, ToJSON b) => ToJSON (S.Pair a b) where -- | @since 1.5.3.0 instance ToJSON2 S.Pair where - liftToJSON2 toa toas tob tobs = liftToJSON2 toa toas tob tobs . S.toLazy - liftToEncoding2 toa toas tob tobs = liftToEncoding2 toa toas tob tobs . S.toLazy + liftToJSON2 oa toa toas ob tob tobs = liftToJSON2 oa toa toas ob tob tobs . S.toLazy + liftToEncoding2 oa toa toas ob tob tobs = liftToEncoding2 oa toa toas ob tob tobs . S.toLazy -- | @since 1.5.3.0 instance ToJSON a => ToJSON1 (S.Pair a) where - liftToJSON toa tos = liftToJSON toa tos . S.toLazy - liftToEncoding toa tos = liftToEncoding toa tos . S.toLazy + liftToJSON oa toa tos = liftToJSON oa toa tos . S.toLazy + liftToEncoding oa toa tos = liftToEncoding oa toa tos . S.toLazy -- | @since 1.5.3.0 instance (ToJSON a, ToJSON b) => ToJSON (S.Either a b) where @@ -2133,48 +2229,54 @@ instance (ToJSON a, ToJSON b) => ToJSON (S.Either a b) where -- | @since 1.5.3.0 instance ToJSON2 S.Either where - liftToJSON2 toa toas tob tobs = liftToJSON2 toa toas tob tobs . S.toLazy - liftToEncoding2 toa toas tob tobs = liftToEncoding2 toa toas tob tobs . S.toLazy + liftToJSON2 oa toa toas ob tob tobs = liftToJSON2 oa toa toas ob tob tobs . S.toLazy + liftToEncoding2 oa toa toas ob tob tobs = liftToEncoding2 oa toa toas ob tob tobs . S.toLazy -- | @since 1.5.3.0 instance ToJSON a => ToJSON1 (S.Either a) where - liftToJSON toa tos = liftToJSON toa tos . S.toLazy - liftToEncoding toa tos = liftToEncoding toa tos . S.toLazy + liftToJSON oa toa tos = liftToJSON oa toa tos . S.toLazy + liftToEncoding oa toa tos = liftToEncoding oa toa tos . S.toLazy -- | @since 1.5.3.0 instance ToJSON a => ToJSON (S.Maybe a) where toJSON = toJSON . S.toLazy toEncoding = toEncoding . S.toLazy + omitField = omitField . S.toLazy -- | @since 1.5.3.0 instance ToJSON1 S.Maybe where - liftToJSON toa tos = liftToJSON toa tos . S.toLazy - liftToEncoding toa tos = liftToEncoding toa tos . S.toLazy + liftToJSON oa toa tos = liftToJSON oa toa tos . S.toLazy + liftToEncoding oa toa tos = liftToEncoding oa toa tos . S.toLazy + liftOmitField oa = liftOmitField oa . S.toLazy ------------------------------------------------------------------------------- -- tagged ------------------------------------------------------------------------------- instance ToJSON1 Proxy where - liftToJSON _ _ _ = Null - liftToEncoding _ _ _ = E.null_ + liftToJSON _ _ _ _ = Null + liftToEncoding _ _ _ _ = E.null_ + liftOmitField _ _ = True instance ToJSON (Proxy a) where toJSON _ = Null toEncoding _ = E.null_ - + omitField _ = True instance ToJSON2 Tagged where - liftToJSON2 _ _ t _ (Tagged x) = t x - liftToEncoding2 _ _ t _ (Tagged x) = t x + liftToJSON2 _ _ _ _ t _ (Tagged x) = t x + liftToEncoding2 _ _ _ _ t _ (Tagged x) = t x + liftOmitField2 _ = coerce instance ToJSON1 (Tagged a) where - liftToJSON t _ (Tagged x) = t x - liftToEncoding t _ (Tagged x) = t x + liftToJSON _ t _ (Tagged x) = t x + liftToEncoding _ t _ (Tagged x) = t x + liftOmitField = coerce instance ToJSON b => ToJSON (Tagged a b) where toJSON = toJSON1 toEncoding = toEncoding1 + omitField = coerce (omitField @b) instance ToJSONKey b => ToJSONKey (Tagged a b) where toJSONKey = contramapToJSONKeyFunction unTagged toJSONKey @@ -2196,35 +2298,35 @@ instance (ToJSON a, ToJSON b) => ToJSON (These a b) where -- | @since 1.5.1.0 instance ToJSON2 These where - liftToJSON2 toa _ _tob _ (This a) = object [ "This" .= toa a ] - liftToJSON2 _toa _ tob _ (That b) = object [ "That" .= tob b ] - liftToJSON2 toa _ tob _ (These a b) = object [ "This" .= toa a, "That" .= tob b ] + liftToJSON2 _ toa _ _ _tob _ (This a) = object [ "This" .= toa a ] + liftToJSON2 _ _toa _ _ tob _ (That b) = object [ "That" .= tob b ] + liftToJSON2 _ toa _ _ tob _ (These a b) = object [ "This" .= toa a, "That" .= tob b ] - liftToEncoding2 toa _ _tob _ (This a) = E.pairs $ E.pair "This" (toa a) - liftToEncoding2 _toa _ tob _ (That b) = E.pairs $ E.pair "That" (tob b) - liftToEncoding2 toa _ tob _ (These a b) = E.pairs $ E.pair "This" (toa a) <> E.pair "That" (tob b) + liftToEncoding2 _ toa _ _ _tob _ (This a) = E.pairs $ E.pair "This" (toa a) + liftToEncoding2 _ _toa _ _ tob _ (That b) = E.pairs $ E.pair "That" (tob b) + liftToEncoding2 _ toa _ _ tob _ (These a b) = E.pairs $ E.pair "This" (toa a) <> E.pair "That" (tob b) -- | @since 1.5.1.0 instance ToJSON a => ToJSON1 (These a) where - liftToJSON _tob _ (This a) = object [ "This" .= a ] - liftToJSON tob _ (That b) = object [ "That" .= tob b ] - liftToJSON tob _ (These a b) = object [ "This" .= a, "That" .= tob b ] + liftToJSON _ _tob _ (This a) = object [ "This" .= a ] + liftToJSON _ tob _ (That b) = object [ "That" .= tob b ] + liftToJSON _ tob _ (These a b) = object [ "This" .= a, "That" .= tob b ] - liftToEncoding _tob _ (This a) = E.pairs $ "This" .= a - liftToEncoding tob _ (That b) = E.pairs $ E.pair "That" (tob b) - liftToEncoding tob _ (These a b) = E.pairs $ "This" .= a <> E.pair "That" (tob b) + liftToEncoding _ _tob _ (This a) = E.pairs $ "This" .= a + liftToEncoding _ tob _ (That b) = E.pairs $ E.pair "That" (tob b) + liftToEncoding _ tob _ (These a b) = E.pairs $ "This" .= a <> E.pair "That" (tob b) -- | @since 1.5.1.0 instance (ToJSON1 f, ToJSON1 g) => ToJSON1 (These1 f g) where - liftToJSON tx tl (This1 a) = object [ "This" .= liftToJSON tx tl a ] - liftToJSON tx tl (That1 b) = object [ "That" .= liftToJSON tx tl b ] - liftToJSON tx tl (These1 a b) = object [ "This" .= liftToJSON tx tl a, "That" .= liftToJSON tx tl b ] + liftToJSON o tx tl (This1 a) = object [ "This" .= liftToJSON o tx tl a ] + liftToJSON o tx tl (That1 b) = object [ "That" .= liftToJSON o tx tl b ] + liftToJSON o tx tl (These1 a b) = object [ "This" .= liftToJSON o tx tl a, "That" .= liftToJSON o tx tl b ] - liftToEncoding tx tl (This1 a) = E.pairs $ E.pair "This" (liftToEncoding tx tl a) - liftToEncoding tx tl (That1 b) = E.pairs $ E.pair "That" (liftToEncoding tx tl b) - liftToEncoding tx tl (These1 a b) = E.pairs $ - pair "This" (liftToEncoding tx tl a) `mappend` - pair "That" (liftToEncoding tx tl b) + liftToEncoding o tx tl (This1 a) = E.pairs $ E.pair "This" (liftToEncoding o tx tl a) + liftToEncoding o tx tl (That1 b) = E.pairs $ E.pair "That" (liftToEncoding o tx tl b) + liftToEncoding o tx tl (These1 a b) = E.pairs $ + pair "This" (liftToEncoding o tx tl a) `mappend` + pair "That" (liftToEncoding o tx tl b) -- | @since 1.5.1.0 instance (ToJSON1 f, ToJSON1 g, ToJSON a) => ToJSON (These1 f g a) where @@ -2251,46 +2353,47 @@ instance (ToJSONKey a, ToJSON a) => ToJSONKey [a] where ------------------------------------------------------------------------------- instance ToJSON2 (,) where - liftToJSON2 toA _ toB _ (a, b) = Array $ V.create $ do + liftToJSON2 _ toA _ _ toB _ (a, b) = Array $ V.create $ do mv <- VM.unsafeNew 2 VM.unsafeWrite mv 0 (toA a) VM.unsafeWrite mv 1 (toB b) return mv - liftToEncoding2 toA _ toB _ (a, b) = E.list id [toA a, toB b] + liftToEncoding2 _ toA _ _ toB _ (a, b) = E.list id [toA a, toB b] instance (ToJSON a) => ToJSON1 ((,) a) where - liftToJSON = liftToJSON2 toJSON toJSONList - liftToEncoding = liftToEncoding2 toEncoding toEncodingList + liftToJSON = liftToJSON2 omitField toJSON toJSONList + liftToEncoding = liftToEncoding2 omitField toEncoding toEncodingList instance (ToJSON a, ToJSON b) => ToJSON (a, b) where toJSON = toJSON2 toEncoding = toEncoding2 + -- omitField = omitField2 instance (ToJSON a) => ToJSON2 ((,,) a) where - liftToJSON2 toB _ toC _ (a, b, c) = Array $ V.create $ do + liftToJSON2 _ toB _ _ toC _ (a, b, c) = Array $ V.create $ do mv <- VM.unsafeNew 3 VM.unsafeWrite mv 0 (toJSON a) VM.unsafeWrite mv 1 (toB b) VM.unsafeWrite mv 2 (toC c) return mv - liftToEncoding2 toB _ toC _ (a, b, c) = E.list id + liftToEncoding2 _ toB _ _ toC _ (a, b, c) = E.list id [ toEncoding a , toB b , toC c ] instance (ToJSON a, ToJSON b) => ToJSON1 ((,,) a b) where - liftToJSON = liftToJSON2 toJSON toJSONList - liftToEncoding = liftToEncoding2 toEncoding toEncodingList + liftToJSON = liftToJSON2 omitField toJSON toJSONList + liftToEncoding = liftToEncoding2 omitField toEncoding toEncodingList instance (ToJSON a, ToJSON b, ToJSON c) => ToJSON (a, b, c) where toJSON = toJSON2 toEncoding = toEncoding2 instance (ToJSON a, ToJSON b) => ToJSON2 ((,,,) a b) where - liftToJSON2 toC _ toD _ (a, b, c, d) = Array $ V.create $ do + liftToJSON2 _ toC _ _ toD _ (a, b, c, d) = Array $ V.create $ do mv <- VM.unsafeNew 4 VM.unsafeWrite mv 0 (toJSON a) VM.unsafeWrite mv 1 (toJSON b) @@ -2298,7 +2401,7 @@ instance (ToJSON a, ToJSON b) => ToJSON2 ((,,,) a b) where VM.unsafeWrite mv 3 (toD d) return mv - liftToEncoding2 toC _ toD _ (a, b, c, d) = E.list id + liftToEncoding2 _ toC _ _ toD _ (a, b, c, d) = E.list id [ toEncoding a , toEncoding b , toC c @@ -2306,15 +2409,15 @@ instance (ToJSON a, ToJSON b) => ToJSON2 ((,,,) a b) where ] instance (ToJSON a, ToJSON b, ToJSON c) => ToJSON1 ((,,,) a b c) where - liftToJSON = liftToJSON2 toJSON toJSONList - liftToEncoding = liftToEncoding2 toEncoding toEncodingList + liftToJSON = liftToJSON2 omitField toJSON toJSONList + liftToEncoding = liftToEncoding2 omitField toEncoding toEncodingList instance (ToJSON a, ToJSON b, ToJSON c, ToJSON d) => ToJSON (a, b, c, d) where toJSON = toJSON2 toEncoding = toEncoding2 instance (ToJSON a, ToJSON b, ToJSON c) => ToJSON2 ((,,,,) a b c) where - liftToJSON2 toD _ toE _ (a, b, c, d, e) = Array $ V.create $ do + liftToJSON2 _ toD _ _ toE _ (a, b, c, d, e) = Array $ V.create $ do mv <- VM.unsafeNew 5 VM.unsafeWrite mv 0 (toJSON a) VM.unsafeWrite mv 1 (toJSON b) @@ -2323,7 +2426,7 @@ instance (ToJSON a, ToJSON b, ToJSON c) => ToJSON2 ((,,,,) a b c) where VM.unsafeWrite mv 4 (toE e) return mv - liftToEncoding2 toD _ toE _ (a, b, c, d, e) = E.list id + liftToEncoding2 _ toD _ _ toE _ (a, b, c, d, e) = E.list id [ toEncoding a , toEncoding b , toEncoding c @@ -2332,15 +2435,15 @@ instance (ToJSON a, ToJSON b, ToJSON c) => ToJSON2 ((,,,,) a b c) where ] instance (ToJSON a, ToJSON b, ToJSON c, ToJSON d) => ToJSON1 ((,,,,) a b c d) where - liftToJSON = liftToJSON2 toJSON toJSONList - liftToEncoding = liftToEncoding2 toEncoding toEncodingList + liftToJSON = liftToJSON2 omitField toJSON toJSONList + liftToEncoding = liftToEncoding2 omitField toEncoding toEncodingList instance (ToJSON a, ToJSON b, ToJSON c, ToJSON d, ToJSON e) => ToJSON (a, b, c, d, e) where toJSON = toJSON2 toEncoding = toEncoding2 instance (ToJSON a, ToJSON b, ToJSON c, ToJSON d) => ToJSON2 ((,,,,,) a b c d) where - liftToJSON2 toE _ toF _ (a, b, c, d, e, f) = Array $ V.create $ do + liftToJSON2 _ toE _ _ toF _ (a, b, c, d, e, f) = Array $ V.create $ do mv <- VM.unsafeNew 6 VM.unsafeWrite mv 0 (toJSON a) VM.unsafeWrite mv 1 (toJSON b) @@ -2350,7 +2453,7 @@ instance (ToJSON a, ToJSON b, ToJSON c, ToJSON d) => ToJSON2 ((,,,,,) a b c d) w VM.unsafeWrite mv 5 (toF f) return mv - liftToEncoding2 toE _ toF _ (a, b, c, d, e, f) = E.list id + liftToEncoding2 _ toE _ _ toF _ (a, b, c, d, e, f) = E.list id [ toEncoding a , toEncoding b , toEncoding c @@ -2360,15 +2463,15 @@ instance (ToJSON a, ToJSON b, ToJSON c, ToJSON d) => ToJSON2 ((,,,,,) a b c d) w ] instance (ToJSON a, ToJSON b, ToJSON c, ToJSON d, ToJSON e) => ToJSON1 ((,,,,,) a b c d e) where - liftToJSON = liftToJSON2 toJSON toJSONList - liftToEncoding = liftToEncoding2 toEncoding toEncodingList + liftToJSON = liftToJSON2 omitField toJSON toJSONList + liftToEncoding = liftToEncoding2 omitField toEncoding toEncodingList instance (ToJSON a, ToJSON b, ToJSON c, ToJSON d, ToJSON e, ToJSON f) => ToJSON (a, b, c, d, e, f) where toJSON = toJSON2 toEncoding = toEncoding2 instance (ToJSON a, ToJSON b, ToJSON c, ToJSON d, ToJSON e) => ToJSON2 ((,,,,,,) a b c d e) where - liftToJSON2 toF _ toG _ (a, b, c, d, e, f, g) = Array $ V.create $ do + liftToJSON2 _ toF _ _ toG _ (a, b, c, d, e, f, g) = Array $ V.create $ do mv <- VM.unsafeNew 7 VM.unsafeWrite mv 0 (toJSON a) VM.unsafeWrite mv 1 (toJSON b) @@ -2379,7 +2482,7 @@ instance (ToJSON a, ToJSON b, ToJSON c, ToJSON d, ToJSON e) => ToJSON2 ((,,,,,,) VM.unsafeWrite mv 6 (toG g) return mv - liftToEncoding2 toF _ toG _ (a, b, c, d, e, f, g) = E.list id + liftToEncoding2 _ toF _ _ toG _ (a, b, c, d, e, f, g) = E.list id [ toEncoding a , toEncoding b , toEncoding c @@ -2390,15 +2493,15 @@ instance (ToJSON a, ToJSON b, ToJSON c, ToJSON d, ToJSON e) => ToJSON2 ((,,,,,,) ] instance (ToJSON a, ToJSON b, ToJSON c, ToJSON d, ToJSON e, ToJSON f) => ToJSON1 ((,,,,,,) a b c d e f) where - liftToJSON = liftToJSON2 toJSON toJSONList - liftToEncoding = liftToEncoding2 toEncoding toEncodingList + liftToJSON = liftToJSON2 omitField toJSON toJSONList + liftToEncoding = liftToEncoding2 omitField toEncoding toEncodingList instance (ToJSON a, ToJSON b, ToJSON c, ToJSON d, ToJSON e, ToJSON f, ToJSON g) => ToJSON (a, b, c, d, e, f, g) where toJSON = toJSON2 toEncoding = toEncoding2 instance (ToJSON a, ToJSON b, ToJSON c, ToJSON d, ToJSON e, ToJSON f) => ToJSON2 ((,,,,,,,) a b c d e f) where - liftToJSON2 toG _ toH _ (a, b, c, d, e, f, g, h) = Array $ V.create $ do + liftToJSON2 _ toG _ _ toH _ (a, b, c, d, e, f, g, h) = Array $ V.create $ do mv <- VM.unsafeNew 8 VM.unsafeWrite mv 0 (toJSON a) VM.unsafeWrite mv 1 (toJSON b) @@ -2410,7 +2513,7 @@ instance (ToJSON a, ToJSON b, ToJSON c, ToJSON d, ToJSON e, ToJSON f) => ToJSON2 VM.unsafeWrite mv 7 (toH h) return mv - liftToEncoding2 toG _ toH _ (a, b, c, d, e, f, g, h) = E.list id + liftToEncoding2 _ toG _ _ toH _ (a, b, c, d, e, f, g, h) = E.list id [ toEncoding a , toEncoding b , toEncoding c @@ -2422,15 +2525,15 @@ instance (ToJSON a, ToJSON b, ToJSON c, ToJSON d, ToJSON e, ToJSON f) => ToJSON2 ] instance (ToJSON a, ToJSON b, ToJSON c, ToJSON d, ToJSON e, ToJSON f, ToJSON g) => ToJSON1 ((,,,,,,,) a b c d e f g) where - liftToJSON = liftToJSON2 toJSON toJSONList - liftToEncoding = liftToEncoding2 toEncoding toEncodingList + liftToJSON = liftToJSON2 omitField toJSON toJSONList + liftToEncoding = liftToEncoding2 omitField toEncoding toEncodingList instance (ToJSON a, ToJSON b, ToJSON c, ToJSON d, ToJSON e, ToJSON f, ToJSON g, ToJSON h) => ToJSON (a, b, c, d, e, f, g, h) where toJSON = toJSON2 toEncoding = toEncoding2 instance (ToJSON a, ToJSON b, ToJSON c, ToJSON d, ToJSON e, ToJSON f, ToJSON g) => ToJSON2 ((,,,,,,,,) a b c d e f g) where - liftToJSON2 toH _ toI _ (a, b, c, d, e, f, g, h, i) = Array $ V.create $ do + liftToJSON2 _ toH _ _ toI _ (a, b, c, d, e, f, g, h, i) = Array $ V.create $ do mv <- VM.unsafeNew 9 VM.unsafeWrite mv 0 (toJSON a) VM.unsafeWrite mv 1 (toJSON b) @@ -2443,7 +2546,7 @@ instance (ToJSON a, ToJSON b, ToJSON c, ToJSON d, ToJSON e, ToJSON f, ToJSON g) VM.unsafeWrite mv 8 (toI i) return mv - liftToEncoding2 toH _ toI _ (a, b, c, d, e, f, g, h, i) = E.list id + liftToEncoding2 _ toH _ _ toI _ (a, b, c, d, e, f, g, h, i) = E.list id [ toEncoding a , toEncoding b , toEncoding c @@ -2456,15 +2559,15 @@ instance (ToJSON a, ToJSON b, ToJSON c, ToJSON d, ToJSON e, ToJSON f, ToJSON g) ] instance (ToJSON a, ToJSON b, ToJSON c, ToJSON d, ToJSON e, ToJSON f, ToJSON g, ToJSON h) => ToJSON1 ((,,,,,,,,) a b c d e f g h) where - liftToJSON = liftToJSON2 toJSON toJSONList - liftToEncoding = liftToEncoding2 toEncoding toEncodingList + liftToJSON = liftToJSON2 omitField toJSON toJSONList + liftToEncoding = liftToEncoding2 omitField toEncoding toEncodingList instance (ToJSON a, ToJSON b, ToJSON c, ToJSON d, ToJSON e, ToJSON f, ToJSON g, ToJSON h, ToJSON i) => ToJSON (a, b, c, d, e, f, g, h, i) where toJSON = toJSON2 toEncoding = toEncoding2 instance (ToJSON a, ToJSON b, ToJSON c, ToJSON d, ToJSON e, ToJSON f, ToJSON g, ToJSON h) => ToJSON2 ((,,,,,,,,,) a b c d e f g h) where - liftToJSON2 toI _ toJ _ (a, b, c, d, e, f, g, h, i, j) = Array $ V.create $ do + liftToJSON2 _ toI _ _ toJ _ (a, b, c, d, e, f, g, h, i, j) = Array $ V.create $ do mv <- VM.unsafeNew 10 VM.unsafeWrite mv 0 (toJSON a) VM.unsafeWrite mv 1 (toJSON b) @@ -2478,7 +2581,7 @@ instance (ToJSON a, ToJSON b, ToJSON c, ToJSON d, ToJSON e, ToJSON f, ToJSON g, VM.unsafeWrite mv 9 (toJ j) return mv - liftToEncoding2 toI _ toJ _ (a, b, c, d, e, f, g, h, i, j) = E.list id + liftToEncoding2 _ toI _ _ toJ _ (a, b, c, d, e, f, g, h, i, j) = E.list id [ toEncoding a , toEncoding b , toEncoding c @@ -2492,15 +2595,15 @@ instance (ToJSON a, ToJSON b, ToJSON c, ToJSON d, ToJSON e, ToJSON f, ToJSON g, ] instance (ToJSON a, ToJSON b, ToJSON c, ToJSON d, ToJSON e, ToJSON f, ToJSON g, ToJSON h, ToJSON i) => ToJSON1 ((,,,,,,,,,) a b c d e f g h i) where - liftToJSON = liftToJSON2 toJSON toJSONList - liftToEncoding = liftToEncoding2 toEncoding toEncodingList + liftToJSON = liftToJSON2 omitField toJSON toJSONList + liftToEncoding = liftToEncoding2 omitField toEncoding toEncodingList instance (ToJSON a, ToJSON b, ToJSON c, ToJSON d, ToJSON e, ToJSON f, ToJSON g, ToJSON h, ToJSON i, ToJSON j) => ToJSON (a, b, c, d, e, f, g, h, i, j) where toJSON = toJSON2 toEncoding = toEncoding2 instance (ToJSON a, ToJSON b, ToJSON c, ToJSON d, ToJSON e, ToJSON f, ToJSON g, ToJSON h, ToJSON i) => ToJSON2 ((,,,,,,,,,,) a b c d e f g h i) where - liftToJSON2 toJ _ toK _ (a, b, c, d, e, f, g, h, i, j, k) = Array $ V.create $ do + liftToJSON2 _ toJ _ _ toK _ (a, b, c, d, e, f, g, h, i, j, k) = Array $ V.create $ do mv <- VM.unsafeNew 11 VM.unsafeWrite mv 0 (toJSON a) VM.unsafeWrite mv 1 (toJSON b) @@ -2515,7 +2618,7 @@ instance (ToJSON a, ToJSON b, ToJSON c, ToJSON d, ToJSON e, ToJSON f, ToJSON g, VM.unsafeWrite mv 10 (toK k) return mv - liftToEncoding2 toJ _ toK _ (a, b, c, d, e, f, g, h, i, j, k) = E.list id + liftToEncoding2 _ toJ _ _ toK _ (a, b, c, d, e, f, g, h, i, j, k) = E.list id [ toEncoding a , toEncoding b , toEncoding c @@ -2530,15 +2633,15 @@ instance (ToJSON a, ToJSON b, ToJSON c, ToJSON d, ToJSON e, ToJSON f, ToJSON g, ] instance (ToJSON a, ToJSON b, ToJSON c, ToJSON d, ToJSON e, ToJSON f, ToJSON g, ToJSON h, ToJSON i, ToJSON j) => ToJSON1 ((,,,,,,,,,,) a b c d e f g h i j) where - liftToJSON = liftToJSON2 toJSON toJSONList - liftToEncoding = liftToEncoding2 toEncoding toEncodingList + liftToJSON = liftToJSON2 omitField toJSON toJSONList + liftToEncoding = liftToEncoding2 omitField toEncoding toEncodingList instance (ToJSON a, ToJSON b, ToJSON c, ToJSON d, ToJSON e, ToJSON f, ToJSON g, ToJSON h, ToJSON i, ToJSON j, ToJSON k) => ToJSON (a, b, c, d, e, f, g, h, i, j, k) where toJSON = toJSON2 toEncoding = toEncoding2 instance (ToJSON a, ToJSON b, ToJSON c, ToJSON d, ToJSON e, ToJSON f, ToJSON g, ToJSON h, ToJSON i, ToJSON j) => ToJSON2 ((,,,,,,,,,,,) a b c d e f g h i j) where - liftToJSON2 toK _ toL _ (a, b, c, d, e, f, g, h, i, j, k, l) = Array $ V.create $ do + liftToJSON2 _ toK _ _ toL _ (a, b, c, d, e, f, g, h, i, j, k, l) = Array $ V.create $ do mv <- VM.unsafeNew 12 VM.unsafeWrite mv 0 (toJSON a) VM.unsafeWrite mv 1 (toJSON b) @@ -2554,7 +2657,7 @@ instance (ToJSON a, ToJSON b, ToJSON c, ToJSON d, ToJSON e, ToJSON f, ToJSON g, VM.unsafeWrite mv 11 (toL l) return mv - liftToEncoding2 toK _ toL _ (a, b, c, d, e, f, g, h, i, j, k, l) = E.list id + liftToEncoding2 _ toK _ _ toL _ (a, b, c, d, e, f, g, h, i, j, k, l) = E.list id [ toEncoding a , toEncoding b , toEncoding c @@ -2570,15 +2673,15 @@ instance (ToJSON a, ToJSON b, ToJSON c, ToJSON d, ToJSON e, ToJSON f, ToJSON g, ] instance (ToJSON a, ToJSON b, ToJSON c, ToJSON d, ToJSON e, ToJSON f, ToJSON g, ToJSON h, ToJSON i, ToJSON j, ToJSON k) => ToJSON1 ((,,,,,,,,,,,) a b c d e f g h i j k) where - liftToJSON = liftToJSON2 toJSON toJSONList - liftToEncoding = liftToEncoding2 toEncoding toEncodingList + liftToJSON = liftToJSON2 omitField toJSON toJSONList + liftToEncoding = liftToEncoding2 omitField toEncoding toEncodingList instance (ToJSON a, ToJSON b, ToJSON c, ToJSON d, ToJSON e, ToJSON f, ToJSON g, ToJSON h, ToJSON i, ToJSON j, ToJSON k, ToJSON l) => ToJSON (a, b, c, d, e, f, g, h, i, j, k, l) where toJSON = toJSON2 toEncoding = toEncoding2 instance (ToJSON a, ToJSON b, ToJSON c, ToJSON d, ToJSON e, ToJSON f, ToJSON g, ToJSON h, ToJSON i, ToJSON j, ToJSON k) => ToJSON2 ((,,,,,,,,,,,,) a b c d e f g h i j k) where - liftToJSON2 toL _ toM _ (a, b, c, d, e, f, g, h, i, j, k, l, m) = Array $ V.create $ do + liftToJSON2 _ toL _ _ toM _ (a, b, c, d, e, f, g, h, i, j, k, l, m) = Array $ V.create $ do mv <- VM.unsafeNew 13 VM.unsafeWrite mv 0 (toJSON a) VM.unsafeWrite mv 1 (toJSON b) @@ -2595,7 +2698,7 @@ instance (ToJSON a, ToJSON b, ToJSON c, ToJSON d, ToJSON e, ToJSON f, ToJSON g, VM.unsafeWrite mv 12 (toM m) return mv - liftToEncoding2 toL _ toM _ (a, b, c, d, e, f, g, h, i, j, k, l, m) = E.list id + liftToEncoding2 _ toL _ _ toM _ (a, b, c, d, e, f, g, h, i, j, k, l, m) = E.list id [ toEncoding a , toEncoding b , toEncoding c @@ -2612,15 +2715,15 @@ instance (ToJSON a, ToJSON b, ToJSON c, ToJSON d, ToJSON e, ToJSON f, ToJSON g, ] instance (ToJSON a, ToJSON b, ToJSON c, ToJSON d, ToJSON e, ToJSON f, ToJSON g, ToJSON h, ToJSON i, ToJSON j, ToJSON k, ToJSON l) => ToJSON1 ((,,,,,,,,,,,,) a b c d e f g h i j k l) where - liftToJSON = liftToJSON2 toJSON toJSONList - liftToEncoding = liftToEncoding2 toEncoding toEncodingList + liftToJSON = liftToJSON2 omitField toJSON toJSONList + liftToEncoding = liftToEncoding2 omitField toEncoding toEncodingList instance (ToJSON a, ToJSON b, ToJSON c, ToJSON d, ToJSON e, ToJSON f, ToJSON g, ToJSON h, ToJSON i, ToJSON j, ToJSON k, ToJSON l, ToJSON m) => ToJSON (a, b, c, d, e, f, g, h, i, j, k, l, m) where toJSON = toJSON2 toEncoding = toEncoding2 instance (ToJSON a, ToJSON b, ToJSON c, ToJSON d, ToJSON e, ToJSON f, ToJSON g, ToJSON h, ToJSON i, ToJSON j, ToJSON k, ToJSON l) => ToJSON2 ((,,,,,,,,,,,,,) a b c d e f g h i j k l) where - liftToJSON2 toM _ toN _ (a, b, c, d, e, f, g, h, i, j, k, l, m, n) = Array $ V.create $ do + liftToJSON2 _ toM _ _ toN _ (a, b, c, d, e, f, g, h, i, j, k, l, m, n) = Array $ V.create $ do mv <- VM.unsafeNew 14 VM.unsafeWrite mv 0 (toJSON a) VM.unsafeWrite mv 1 (toJSON b) @@ -2638,7 +2741,7 @@ instance (ToJSON a, ToJSON b, ToJSON c, ToJSON d, ToJSON e, ToJSON f, ToJSON g, VM.unsafeWrite mv 13 (toN n) return mv - liftToEncoding2 toM _ toN _ (a, b, c, d, e, f, g, h, i, j, k, l, m, n) = E.list id + liftToEncoding2 _ toM _ _ toN _ (a, b, c, d, e, f, g, h, i, j, k, l, m, n) = E.list id [ toEncoding a , toEncoding b , toEncoding c @@ -2656,15 +2759,15 @@ instance (ToJSON a, ToJSON b, ToJSON c, ToJSON d, ToJSON e, ToJSON f, ToJSON g, ] instance (ToJSON a, ToJSON b, ToJSON c, ToJSON d, ToJSON e, ToJSON f, ToJSON g, ToJSON h, ToJSON i, ToJSON j, ToJSON k, ToJSON l, ToJSON m) => ToJSON1 ((,,,,,,,,,,,,,) a b c d e f g h i j k l m) where - liftToJSON = liftToJSON2 toJSON toJSONList - liftToEncoding = liftToEncoding2 toEncoding toEncodingList + liftToJSON = liftToJSON2 omitField toJSON toJSONList + liftToEncoding = liftToEncoding2 omitField toEncoding toEncodingList instance (ToJSON a, ToJSON b, ToJSON c, ToJSON d, ToJSON e, ToJSON f, ToJSON g, ToJSON h, ToJSON i, ToJSON j, ToJSON k, ToJSON l, ToJSON m, ToJSON n) => ToJSON (a, b, c, d, e, f, g, h, i, j, k, l, m, n) where toJSON = toJSON2 toEncoding = toEncoding2 instance (ToJSON a, ToJSON b, ToJSON c, ToJSON d, ToJSON e, ToJSON f, ToJSON g, ToJSON h, ToJSON i, ToJSON j, ToJSON k, ToJSON l, ToJSON m) => ToJSON2 ((,,,,,,,,,,,,,,) a b c d e f g h i j k l m) where - liftToJSON2 toN _ toO _ (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o) = Array $ V.create $ do + liftToJSON2 _ toN _ _ toO _ (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o) = Array $ V.create $ do mv <- VM.unsafeNew 15 VM.unsafeWrite mv 0 (toJSON a) VM.unsafeWrite mv 1 (toJSON b) @@ -2683,7 +2786,7 @@ instance (ToJSON a, ToJSON b, ToJSON c, ToJSON d, ToJSON e, ToJSON f, ToJSON g, VM.unsafeWrite mv 14 (toO o) return mv - liftToEncoding2 toN _ toO _ (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o) = E.list id + liftToEncoding2 _ toN _ _ toO _ (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o) = E.list id [ toEncoding a , toEncoding b , toEncoding c @@ -2702,8 +2805,8 @@ instance (ToJSON a, ToJSON b, ToJSON c, ToJSON d, ToJSON e, ToJSON f, ToJSON g, ] instance (ToJSON a, ToJSON b, ToJSON c, ToJSON d, ToJSON e, ToJSON f, ToJSON g, ToJSON h, ToJSON i, ToJSON j, ToJSON k, ToJSON l, ToJSON m, ToJSON n) => ToJSON1 ((,,,,,,,,,,,,,,) a b c d e f g h i j k l m n) where - liftToJSON = liftToJSON2 toJSON toJSONList - liftToEncoding = liftToEncoding2 toEncoding toEncodingList + liftToJSON = liftToJSON2 omitField toJSON toJSONList + liftToEncoding = liftToEncoding2 omitField toEncoding toEncodingList instance (ToJSON a, ToJSON b, ToJSON c, ToJSON d, ToJSON e, ToJSON f, ToJSON g, ToJSON h, ToJSON i, ToJSON j, ToJSON k, ToJSON l, ToJSON m, ToJSON n, ToJSON o) => ToJSON (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o) where toJSON = toJSON2 diff --git a/tests/Encoders.hs b/tests/Encoders.hs index 844e5bcbc..d8099331f 100644 --- a/tests/Encoders.hs +++ b/tests/Encoders.hs @@ -3,6 +3,7 @@ {-# LANGUAGE GADTs #-} {-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE TemplateHaskell #-} +{-# OPTIONS_GHC -ddump-splices -ddump-to-file #-} module Encoders (module Encoders) where @@ -117,11 +118,11 @@ gNullaryFromJSONKey t = case genericFromJSONKey keyOptions of -- Unary types type LiftToJSON f a = - (a -> Value) -> ([a] -> Value) -> f a -> Value + (a -> Bool) -> (a -> Value) -> ([a] -> Value) -> f a -> Value type LiftToEncoding f a = - (a -> Encoding) -> ([a] -> Encoding) -> f a -> Encoding + (a -> Bool) -> (a -> Encoding) -> ([a] -> Encoding) -> f a -> Encoding type LiftParseJSON f a = - (Value -> Parser a) -> (Value -> Parser [a]) -> Value -> Parser (f a) + Maybe a -> (Value -> Parser a) -> (Value -> Parser [a]) -> Value -> Parser (f a) thSomeTypeToJSON2ElemArray :: SomeType Int -> Value thSomeTypeToJSON2ElemArray = $(mkToJSON opts2ElemArray ''SomeType) @@ -296,29 +297,6 @@ gOptionFieldParseJSON = genericParseJSON optsOptionField thMaybeFieldToJSON :: MaybeField -> Value thMaybeFieldToJSON = $(mkToJSON optsOptionField 'MaybeField) - --------------------------------------------------------------------------------- --- IncoherentInstancesNeeded --------------------------------------------------------------------------------- - --- | This test demonstrates the need for IncoherentInstances. See the definition --- of 'IncoherentInstancesNeeded' for a discussion of the issue. --- --- NOTE 1: We only need to compile this test. We do not need to run it. --- --- NOTE 2: We actually only use the INCOHERENT pragma on specific instances --- instead of the IncoherentInstances language extension. Therefore, this is --- only supported on GHC versions >= 7.10. -incoherentInstancesNeededParseJSONString :: FromJSON a => Value -> Parser (IncoherentInstancesNeeded a) -incoherentInstancesNeededParseJSONString = case () of - _ | True -> $(mkParseJSON defaultOptions ''IncoherentInstancesNeeded) - | False -> genericParseJSON defaultOptions - -incoherentInstancesNeededToJSON :: ToJSON a => IncoherentInstancesNeeded a -> Value -incoherentInstancesNeededToJSON = case () of - _ | True -> $(mkToJSON defaultOptions ''IncoherentInstancesNeeded) - | False -> genericToJSON defaultOptions - ------------------------------------------------------------------------------- -- EitherTextInt encoders/decodes ------------------------------------------------------------------------------- diff --git a/tests/PropUtils.hs b/tests/PropUtils.hs index d685ea9e5..19b54fe7f 100644 --- a/tests/PropUtils.hs +++ b/tests/PropUtils.hs @@ -39,7 +39,7 @@ import Data.Hashable (Hashable) import Data.Map (Map) import Encoders import Instances () -import Test.QuickCheck (Arbitrary(..), Property, Testable, (===), (.&&.), counterexample) +import Test.QuickCheck (Arbitrary(..), Property, Testable, (===), (.&&.), counterexample, property) import Types import Text.Read (readMaybe) import qualified Data.ByteString.Lazy.Char8 as L @@ -72,8 +72,8 @@ toParseJSON1 -> Property toParseJSON1 parsejson1 tojson1 = toParseJSON parsejson tojson where - parsejson = parsejson1 parseJSON (listParser parseJSON) - tojson = tojson1 toJSON (listValue toJSON) + parsejson = parsejson1 omittedField parseJSON (listParser parseJSON) + tojson = tojson1 omitField toJSON (listValue toJSON) roundTripEnc :: (FromJSON a, ToJSON a, Show a) => (a -> a -> Property) -> a -> Property @@ -96,8 +96,23 @@ roundTripNoEnc eq i = (ISuccess v) -> v `eq` i (IError path err) -> failure "fromJSON" (formatError path err) i +roundTripOmit :: (FromJSON a, ToJSON a, Show a) => + (Maybe a -> Maybe a -> Property) -> a -> Property +roundTripOmit eq i + | omitField i = omf `eq` Just i + | otherwise = case fmap omitField omf of + Nothing -> property True + Just True -> property True + Just False -> counterexample (show omf) False + where + omf = omittedField + roundTripEq :: (Eq a, FromJSON a, ToJSON a, Show a) => a -> Property -roundTripEq y = roundTripEnc (===) y .&&. roundTripNoEnc (===) y .&&. roundTripDecEnc (===) y +roundTripEq y = + roundTripEnc (===) y .&&. + roundTripNoEnc (===) y .&&. + roundTripDecEnc (===) y .&&. + roundTripOmit (===) y roundtripReadShow :: Value -> Property roundtripReadShow v = readMaybe (show v) === Just v @@ -161,9 +176,9 @@ sameAs1 -> Property sameAs1 toVal1 toEnc1 v = lhs === rhs where - rhs = Right $ toVal1 toJSON (listValue toJSON) v + rhs = Right $ toVal1 omitField toJSON (listValue toJSON) v lhs = eitherDecode . encodingToLazyByteString $ - toEnc1 toEncoding (listEncoding toEncoding) v + toEnc1 omitField toEncoding (listEncoding toEncoding) v sameAs1Agree :: ToJSON a @@ -174,7 +189,7 @@ sameAs1Agree sameAs1Agree toEnc toEnc1 v = rhs === lhs where rhs = encodingToLazyByteString $ toEnc v - lhs = encodingToLazyByteString $ toEnc1 toEncoding (listEncoding toEncoding) v + lhs = encodingToLazyByteString $ toEnc1 omitField toEncoding (listEncoding toEncoding) v -------------------------------------------------------------------------------- -- Value properties diff --git a/tests/PropertyRoundTrip.hs b/tests/PropertyRoundTrip.hs index 95f68d26c..1ec4b4785 100644 --- a/tests/PropertyRoundTrip.hs +++ b/tests/PropertyRoundTrip.hs @@ -27,6 +27,7 @@ import Numeric.Natural (Natural) import Test.Tasty (TestTree, testGroup) import Test.Tasty.QuickCheck (testProperty) import Types +import qualified Data.Monoid as Monoid import qualified Data.Text as T import qualified Data.Text.Lazy as LT import qualified Data.Text.Short as ST @@ -41,8 +42,9 @@ import Instances () roundTripTests :: TestTree roundTripTests = - testGroup "roundTrip" [ - testProperty "Value" $ roundTripEq @Value + testGroup "roundTrip" + [ testProperty "()" $ roundTripEq @() + , testProperty "Value" $ roundTripEq @Value , testProperty "Bool" $ roundTripEq @Bool , testProperty "Double" $ roundTripEq @(Approx Double) , testProperty "Int" $ roundTripEq @Int @@ -82,6 +84,8 @@ roundTripTests = , testProperty "Fix" $ roundTripEq @(F.Fix (These Char)) , testProperty "Mu" $ roundTripEq @(F.Mu (These Char)) , testProperty "Nu" $ roundTripEq @(F.Nu (These Char)) + , testProperty "Maybe" $ roundTripEq @(Maybe Int) + , testProperty "Monoid.First" $ roundTripEq @(Monoid.First Int) , testProperty "Strict Pair" $ roundTripEq @(S.Pair Int Char) , testProperty "Strict Either" $ roundTripEq @(S.Either Int Char) , testProperty "Strict These" $ roundTripEq @(S.These Int Char) diff --git a/tests/Regression/Issue571.hs b/tests/Regression/Issue571.hs index f3891288c..9407f8e55 100644 --- a/tests/Regression/Issue571.hs +++ b/tests/Regression/Issue571.hs @@ -10,14 +10,33 @@ import Data.Aeson data F = F { a :: Maybe Int , b :: Maybe Int + , c :: () } deriving (Eq, Show, Generic) instance FromJSON F where parseJSON = genericParseJSON defaultOptions { omitNothingFields = False } -- default +data G = G + { e :: Maybe Int + , f :: Maybe Int + , g :: () + } + deriving (Eq, Show, Generic) + +instance FromJSON G where + parseJSON = genericParseJSON defaultOptions { omitNothingFields = False, allowOmittedFields = False } + + issue571 :: TestTree issue571 = testCase "issue571" $ do -- the Maybe fields can be omitted. - let actual = decode "{}" :: Maybe F - actual @?= Just F { a = Nothing, b = Nothing } + let actualF = decode "{}" :: Maybe F + actualF @?= Just F { a = Nothing, b = Nothing, c = () } + + (decode "{}" :: Maybe G) @?= Nothing + (decode "{\"e\":1, \"f\":2}" :: Maybe G) @?= Nothing + (decode "{\"e\":1, \"g\":[]}" :: Maybe G) @?= Nothing + (decode "{\"f\":2, \"g\":[]}" :: Maybe G) @?= Nothing + (decode "{\"e\":1, \"f\":2, \"g\":[]}" :: Maybe G) @?= Just G { e = Just 1, f = Just 2, g = () } + (decode "{\"e\":1, \"f\":2, \"g\":true}" :: Maybe G) @?= Just G { e = Just 1, f = Just 2, g = () } diff --git a/tests/Regression/Issue687.hs b/tests/Regression/Issue687.hs new file mode 100644 index 000000000..c56a22cf1 --- /dev/null +++ b/tests/Regression/Issue687.hs @@ -0,0 +1,43 @@ +{-# LANGUAGE DeriveGeneric, TypeApplications, OverloadedStrings, TemplateHaskell, DuplicateRecordFields #-} +module Regression.Issue687 where + +import GHC.Generics (Generic1) +import Data.Aeson +import Data.Aeson.Types (iparseEither) +import Data.Aeson.Encoding (encodingToLazyByteString) +import Data.Aeson.TH (deriveJSON1) +import Test.Tasty (TestTree) +import Test.Tasty.HUnit (testCase, (@?=)) + +data ExG a = ExG { required :: a, optional :: Maybe a } + deriving (Eq, Show, Generic1) + +data ExTH a = ExTH { required :: a, optional :: Maybe a } + deriving (Eq, Show, Generic1) + +instance ToJSON1 ExG where + liftToJSON = genericLiftToJSON defaultOptions { omitNothingFields = True } + liftToEncoding = genericLiftToEncoding defaultOptions { omitNothingFields = True } + +instance FromJSON1 ExG where + liftParseJSON = genericLiftParseJSON defaultOptions { omitNothingFields = True } + +$(deriveJSON1 defaultOptions { omitNothingFields = True } ''ExTH) + +issue687 :: TestTree +issue687 = testCase "issue687" $ do + example (ExG @Int 1 Nothing) $ object [ "required" .= (1 :: Int) ] + example (ExG @Int 1 (Just 2)) $ object [ "required" .= (1 :: Int), "optional" .= (2 :: Int) ] + + example (ExTH @Int 1 Nothing) $ object [ "required" .= (1 :: Int) ] + example (ExTH @Int 1 (Just 2)) $ object [ "required" .= (1 :: Int), "optional" .= (2 :: Int) ] + + where + example :: (ToJSON1 f, FromJSON1 f, Eq (f Int), Show (f Int)) => f Int -> Value -> IO () + example x val = do + -- encoding + toJSON1 x @?= val + decode (encodingToLazyByteString (toEncoding1 x)) @?= Just val + + -- decoding + iparseEither parseJSON1 val @?= Right x diff --git a/tests/Types.hs b/tests/Types.hs index bc5ab6657..de5fd4e35 100644 --- a/tests/Types.hs +++ b/tests/Types.hs @@ -81,18 +81,6 @@ data SomeType a = Nullary | List [a] deriving (Eq, Show) --- | This type requires IncoherentInstances for the instances of the type --- classes Data.Aeson.TH.LookupField and Data.Aeson.Types.FromJSON.FromRecord. --- --- The minimum known requirements for this type are: --- * Record type with at least two fields --- * One field type is either a type parameter or a type/data family --- * Another field type is a @Maybe@ of the above field type -data IncoherentInstancesNeeded a = IncoherentInstancesNeeded - { incoherentInstancesNeededMaybeNot :: a - , incoherentInstancesNeededMaybeYes :: Maybe a - } deriving Generic - -- Used for testing UntaggedValue SumEncoding data EitherTextInt = LeftBool Bool diff --git a/tests/UnitTests.hs b/tests/UnitTests.hs index 2985d6117..b208b9469 100644 --- a/tests/UnitTests.hs +++ b/tests/UnitTests.hs @@ -60,15 +60,18 @@ import qualified Data.Text.Lazy.Encoding as TLE import qualified ErrorMessages import qualified SerializationFormatSpec -import UnitTests.NullaryConstructors (nullaryConstructors) import Regression.Issue351 import Regression.Issue571 +import Regression.Issue687 import Regression.Issue967 -import UnitTests.Hashable +import UnitTests.OmitNothingFieldsNote import UnitTests.FromJSONKey -import UnitTests.UTCTime -import UnitTests.MonadFix +import UnitTests.Hashable import UnitTests.KeyMapInsertWith +import UnitTests.MonadFix +import UnitTests.NullaryConstructors (nullaryConstructors) +import UnitTests.OptionalFields (optionalFields) +import UnitTests.UTCTime roundTripCamel :: String -> Assertion roundTripCamel name = assertEqual "" name (camelFrom '_' $ camelTo '_' name) @@ -260,7 +263,7 @@ deriveToJSON1 defaultOptions ''Foo pr455 :: Assertion pr455 = assertEqual "FooCons FooNil" - (toJSON foo) (liftToJSON undefined undefined foo) + (toJSON foo) (liftToJSON undefined undefined undefined foo) where foo :: Foo Int foo = FooCons FooNil @@ -274,6 +277,7 @@ showOptions = ++ ", constructorTagModifier =~ \"ExampleConstructor\"" ++ ", allNullaryToStringTag = True" ++ ", omitNothingFields = False" + ++ ", allowOmittedFields = True" ++ ", sumEncoding = TaggedObject {tagFieldName = \"tag\", contentsFieldName = \"contents\"}" ++ ", unwrapUnaryRecords = False" ++ ", tagSingleConstructors = False" @@ -537,6 +541,7 @@ tests = testGroup "unit" [ , testGroup "Object construction" $ fmap (testCase "-") objectConstruction , testGroup "Nullary constructors" $ fmap (testCase "-") nullaryConstructors , fromJSONKeyTests + , optionalFields , testCase "PR #455" pr455 , testCase "Unescape string (PR #477)" unescapeString , testCase "Show Options" showOptions @@ -560,6 +565,8 @@ tests = testGroup "unit" [ , monadFixTests , issue351 , issue571 + , issue687 , issue967 , keyMapInsertWithTests + , omitNothingFieldsNoteTests ] diff --git a/tests/UnitTests/MonadFix.hs b/tests/UnitTests/MonadFix.hs index 63a496956..f985dfccc 100644 --- a/tests/UnitTests/MonadFix.hs +++ b/tests/UnitTests/MonadFix.hs @@ -66,7 +66,7 @@ monadFixParserA = withObject "Rec" $ \obj -> mdo let p' :: Value -> Data.Aeson.Types.Parser [Char] p' v = do - (c, cs) <- liftParseJSON p'' (listParser p'') v + (c, cs) <- liftParseJSON Nothing p'' (listParser p'') v return (c : cs) foo <- explicitParseField p' obj "foo" @@ -90,7 +90,7 @@ monadFixParserB = withObject "Rec" $ \obj -> mdo let p' :: Value -> Data.Aeson.Types.Parser [Char] p' v = do - (c, cs) <- liftParseJSON p'' (listParser p'') v + (c, cs) <- liftParseJSON Nothing p'' (listParser p'') v return (c : cs) refs <- traverse p' (KM.toMap obj) diff --git a/tests/UnitTests/OmitNothingFieldsNote.hs b/tests/UnitTests/OmitNothingFieldsNote.hs new file mode 100644 index 000000000..91c13b491 --- /dev/null +++ b/tests/UnitTests/OmitNothingFieldsNote.hs @@ -0,0 +1,62 @@ +{-# LANGUAGE DeriveGeneric, OverloadedStrings #-} +module UnitTests.OmitNothingFieldsNote (omitNothingFieldsNoteTests) where + +-- prior aeson-2.2 the 'omitNothingFields' had the following note, +-- which is no longer true as these tests illustrate. + +-- Setting 'omitNothingFields' to 'True' only affects fields which are of +-- type 'Maybe' /uniformly/ in the 'ToJSON' instance. +-- In particular, if the type of a field is declared as a type variable, it +-- will not be omitted from the JSON object, unless the field is +-- specialized upfront in the instance. +-- +-- The same holds for 'Maybe' fields being optional in the 'FromJSON' instance. +-- +-- ==== __Example__ +-- +-- The generic instance for the following type @Fruit@ depends on whether +-- the instance head is @Fruit a@ or @Fruit (Maybe a)@. +-- +-- @ +-- data Fruit a = Fruit +-- { apples :: a -- A field whose type is a type variable. +-- , oranges :: 'Maybe' Int +-- } deriving 'Generic' +-- +-- -- apples required, oranges optional +-- -- Even if 'Data.Aeson.fromJSON' is then specialized to (Fruit ('Maybe' a)). +-- instance 'Data.Aeson.FromJSON' a => 'Data.Aeson.FromJSON' (Fruit a) +-- +-- -- apples optional, oranges optional +-- -- In this instance, the field apples is uniformly of type ('Maybe' a). +-- instance 'Data.Aeson.FromJSON' a => 'Data.Aeson.FromJSON' (Fruit ('Maybe' a)) +-- +-- options :: 'Options' +-- options = 'defaultOptions' { 'omitNothingFields' = 'True' } +-- +-- -- apples always present in the output, oranges is omitted if 'Nothing' +-- instance 'Data.Aeson.ToJSON' a => 'Data.Aeson.ToJSON' (Fruit a) where +-- 'Data.Aeson.toJSON' = 'Data.Aeson.genericToJSON' options +-- +-- -- both apples and oranges are omitted if 'Nothing' +-- instance 'Data.Aeson.ToJSON' a => 'Data.Aeson.ToJSON' (Fruit ('Maybe' a)) where +-- 'Data.Aeson.toJSON' = 'Data.Aeson.genericToJSON' options + +import Test.Tasty (TestTree) +import Test.Tasty.HUnit (testCase, (@?=)) +import GHC.Generics (Generic) +import Data.Aeson + +omitNothingFieldsNoteTests :: TestTree +omitNothingFieldsNoteTests = testCase "omitNothingFields Note" $ do + -- both fields are omitted, not only oranges! + encode (Fruit (Nothing :: Maybe Int) Nothing) @?= "{}" + +data Fruit a = Fruit + { apples :: a -- A field whose type is a type variable. + , oranges :: Maybe Int + } deriving Generic + +instance ToJSON a => ToJSON (Fruit a) where + toJSON = genericToJSON defaultOptions { omitNothingFields = True } + toEncoding = genericToEncoding defaultOptions { omitNothingFields = True } diff --git a/tests/UnitTests/OptionalFields.hs b/tests/UnitTests/OptionalFields.hs new file mode 100644 index 000000000..6e645b55f --- /dev/null +++ b/tests/UnitTests/OptionalFields.hs @@ -0,0 +1,130 @@ +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE OverloadedStrings #-} + +module UnitTests.OptionalFields (optionalFields) where + +import GHC.Generics (Generic) +import Data.Maybe (isNothing) +import UnitTests.OptionalFields.Common +import UnitTests.OptionalFields.Generics (omitGenerics) +import UnitTests.OptionalFields.TH (omitTH) +import UnitTests.OptionalFields.Manual (omitManual) + +optionalFields :: TestTree +optionalFields = testGroup "optional fields" + [ omitGenerics + , omitTH + , omitManual + , proofOfConcept + ] + +-- c.f. https://github.com/haskell/aeson/pull/839#issuecomment-782453060 +data P = P + { x :: Nullable Int -- Field is required, but can be null. + , y :: Undefineable Int -- Field is optional, but cannot be null. + , z :: NullOrUndefineable Int -- Field is optional, and can be null. + } + deriving (Eq, Show, Generic) + +instance ToJSON P where + toJSON = genericToJSON opts + toEncoding = genericToEncoding opts + +instance FromJSON P where + parseJSON = genericParseJSON opts + +newtype Nullable a = Nullable (Maybe a) + deriving (Eq, Show, Generic) + +instance ToJSON a => ToJSON (Nullable a) where + toJSON = genericToJSON opts + toEncoding = genericToEncoding opts + +instance FromJSON a => FromJSON (Nullable a) where + parseJSON = genericParseJSON opts + +newtype Undefineable a = Undefineable (Maybe a) + deriving (Eq, Show, Generic) + +instance ToJSON a => ToJSON (Undefineable a) where + toJSON = genericToJSON opts + toEncoding = genericToEncoding opts + omitField (Undefineable a) = isNothing a + +instance FromJSON a => FromJSON (Undefineable a) where + parseJSON Null = fail "Undefineable.parseJSON: expected non-null value" + parseJSON v = genericParseJSON opts v + omittedField = Just (Undefineable Nothing) + +newtype NullOrUndefineable a = NullOrUndefineable (Maybe a) + deriving (Eq, Show, Generic) + +instance ToJSON a => ToJSON (NullOrUndefineable a) where + toJSON = genericToJSON opts + toEncoding = genericToEncoding opts + omitField (NullOrUndefineable a) = isNothing a + +instance FromJSON a => FromJSON (NullOrUndefineable a) where + parseJSON = genericParseJSON opts + omittedField = Just (NullOrUndefineable Nothing) + +opts :: Options +opts = defaultOptions { omitNothingFields = True } + +fullP :: P +fullP = P (Nullable $ Just 0) (Undefineable $ Just 0) (NullOrUndefineable $ Just 0) + +zero :: Key -> (Key, Value) +zero k = k .= (0 :: Int) + +proofOfConcept :: TestTree +proofOfConcept = testGroup "Type-directed optional fields Proof of Concept" + [ testGroup "toJSON" + [ testCase "x is not omitted when Nothing" $ + let subject = fullP {x = Nullable Nothing} + expected = object ["x" .= Null, zero "y", zero "z"] + in toJSON subject @?= expected + + , testCase "y is omitted when Nothing" $ + let subject = fullP {y = Undefineable Nothing} + expected = object [zero "x", zero "z"] + in toJSON subject @?= expected + + , testCase "z is omitted when Nothing" $ + let subject = fullP {z = NullOrUndefineable Nothing} + expected = object [zero "x", zero "y"] + in toJSON subject @?= expected + ] + + , testGroup "parseJSON" + [ testCase "x can be null" $ + let subject = object ["x" .= Null, zero "y", zero "z"] + expected = Just fullP {x = Nullable Nothing} + in decode (encode subject) @?= expected + + , testCase "x cannot be omitted" $ + let subject = object [zero "y", zero "z"] + expected = Nothing :: Maybe P + in decode (encode subject) @?= expected + + , testCase "y can be omitted" $ + let subject = object [zero "x", zero "z"] + expected = Just fullP {y = Undefineable Nothing} + in decode (encode subject) @?= expected + + , testCase "y cannot be null" $ + let subject = object [zero "x", "y" .= Null, zero "z"] + expected = Nothing :: Maybe P + in decode (encode subject) @?= expected + + , testCase "z can be null" $ + let subject = object [zero "x", zero "y", "z" .= Null] + expected = Just fullP {z = NullOrUndefineable Nothing} + in decode (encode subject) @?= expected + + , testCase "z can be omitted" $ + let subject = object [zero "x", zero "y"] + expected = Just fullP {z = NullOrUndefineable Nothing} + in decode (encode subject) @?= expected + ] + ] diff --git a/tests/UnitTests/OptionalFields/Common.hs b/tests/UnitTests/OptionalFields/Common.hs new file mode 100644 index 000000000..1afe2737f --- /dev/null +++ b/tests/UnitTests/OptionalFields/Common.hs @@ -0,0 +1,214 @@ +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE DuplicateRecordFields #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE UndecidableInstances #-} +{-# LANGUAGE OverloadedStrings #-} + +module UnitTests.OptionalFields.Common + ( module UnitTests.OptionalFields.Common + , module Data.Aeson + , module Data.Aeson.Types + , module Data.Aeson.TH + , module Test.Tasty + , module Test.Tasty.HUnit + , module Data.Proxy + ) where + +import Data.Aeson +import Data.Aeson.Types +import Data.Aeson.TH +import Data.Maybe (isNothing) +import GHC.Generics (Generic, Generic1) +import Data.Proxy +import Test.Tasty +import Test.Tasty.HUnit +import qualified Data.Text as T + +------------------------------------------------------------------------------- +-- Field types +------------------------------------------------------------------------------- + +newtype NullableNonEmptyString = NullableNonEmptyString (Maybe String) + deriving (Eq, Show) + +defaultNullableNonEmptyString :: NullableNonEmptyString +defaultNullableNonEmptyString = NullableNonEmptyString Nothing + +instance ToJSON NullableNonEmptyString where + toJSON (NullableNonEmptyString x) = toJSON x + toEncoding (NullableNonEmptyString x) = toEncoding x + omitField (NullableNonEmptyString x) = isNothing x + +instance FromJSON NullableNonEmptyString where + parseJSON Null = pure defaultNullableNonEmptyString + parseJSON (String x) = pure (nne $ T.unpack x) + parseJSON _ = fail "NullableNonEmptyString.parseJSON: expected String or Null" + + omittedField = Just defaultNullableNonEmptyString + + + +nne :: String -> NullableNonEmptyString +nne str = case filter (/= ' ') str of + "" -> NullableNonEmptyString Nothing + _ -> NullableNonEmptyString (Just str) + +newtype Default = Default Int + deriving (Eq, Show) + +instance ToJSON Default where + toJSON (Default i) = toJSON i + toEncoding (Default i) = toEncoding i + omitField (Default i) = i == 0 + +instance FromJSON Default where + parseJSON = fmap Default . parseJSON + omittedField = Just (Default 0) + +------------------------------------------------------------------------------- +-- Records +------------------------------------------------------------------------------- + +-- lax +data RecordA = RecordA + { required :: String + , optional :: NullableNonEmptyString + , default_ :: Default + } + deriving (Eq, Show, Generic) + +-- strict +data RecordB = RecordB + { required :: String + , optional :: NullableNonEmptyString + , default_ :: Default + } + deriving (Eq, Show, Generic) + +-- default +data RecordC = RecordC + { required :: String + , optional :: NullableNonEmptyString + , default_ :: Default + } + deriving (Eq, Show, Generic) + +data HRecordA a = HRecordA + { required :: String + , optional :: a + , default_ :: Default + } + deriving (Eq, Show, Generic1) + +data HRecordB a = HRecordB + { required :: String + , optional :: a + , default_ :: Default + } + deriving (Eq, Show, Generic1) + +data HRecordC a = HRecordC + { required :: String + , optional :: a + , default_ :: Default + } + deriving (Eq, Show, Generic1) + +type HRecordA' = HRecordA NullableNonEmptyString +type HRecordB' = HRecordB NullableNonEmptyString +type HRecordC' = HRecordC NullableNonEmptyString + +------------------------------------------------------------------------------- +-- Options +------------------------------------------------------------------------------- + +nonOmittingOptions :: Options +nonOmittingOptions = defaultOptions { omitNothingFields = False, allowOmittedFields = False } + +omittingOptions :: Options +omittingOptions = defaultOptions { omitNothingFields = True, allowOmittedFields = True } + +------------------------------------------------------------------------------- +-- Test utils +------------------------------------------------------------------------------- + +encodeCase :: HasCallStack => ToJSON a => a -> Value -> IO () +encodeCase record obj = do + decode @Value (encode record) @?= Just obj + decode @Value (encode (toJSON record)) @?= Just obj + +decodeCase :: forall a. HasCallStack => (FromJSON a, Eq a, Show a) => a -> Value -> IO () +decodeCase record obj = do + decode @a (encode obj) @?= Just record + +counterCase :: forall a proxy. HasCallStack => (FromJSON a, ToJSON a, Show a) => proxy a -> Value -> IO () +counterCase _ obj = case decode @a (encode obj) of + Nothing -> return () + Just v -> assertFailure $ "decode should fail, got: " ++ show v + +------------------------------------------------------------------------------- +-- Test inputs +------------------------------------------------------------------------------- + +helloWorldRecA :: RecordA +helloWorldRecA = RecordA "hello" (nne "world") (Default 42) + +helloWorldRecB :: RecordB +helloWorldRecB = RecordB "hello" (nne "world") (Default 42) + +helloWorldRecC :: RecordC +helloWorldRecC = RecordC "hello" (nne "world") (Default 42) + +helloWorldHRecA :: HRecordA NullableNonEmptyString +helloWorldHRecA = HRecordA "hello" (nne "world") (Default 42) + +helloWorldHRecB :: HRecordB NullableNonEmptyString +helloWorldHRecB = HRecordB "hello" (nne "world") (Default 42) + +helloWorldHRecC :: HRecordC NullableNonEmptyString +helloWorldHRecC = HRecordC "hello" (nne "world") (Default 42) + +helloWorldObj :: Value +helloWorldObj = object + [ "required" .= String "hello" + , "optional" .= String "world" + , "default_" .= Number 42 + ] + +helloRecA :: RecordA +helloRecA = RecordA "hello" defaultNullableNonEmptyString (Default 0) + +helloRecB :: RecordB +helloRecB = RecordB "hello" defaultNullableNonEmptyString (Default 0) + +helloRecC :: RecordC +helloRecC = RecordC "hello" defaultNullableNonEmptyString (Default 0) + +helloHRecA :: HRecordA NullableNonEmptyString +helloHRecA = HRecordA "hello" defaultNullableNonEmptyString (Default 0) + +helloHRecB :: HRecordB NullableNonEmptyString +helloHRecB = HRecordB "hello" defaultNullableNonEmptyString (Default 0) + +helloHRecC :: HRecordC NullableNonEmptyString +helloHRecC = HRecordC "hello" defaultNullableNonEmptyString (Default 0) + +helloObj :: Value +helloObj = object + [ "required" .= String "hello" + ] + +helloNullObj :: Value +helloNullObj = object + [ "required" .= String "hello" + , "optional" .= Null + , "default_" .= Number 0 + ] + +helloNullObj2 :: Value +helloNullObj2 = object + [ "required" .= String "hello" + , "optional" .= Null + , "default_" .= Null + ] diff --git a/tests/UnitTests/OptionalFields/Generics.hs b/tests/UnitTests/OptionalFields/Generics.hs new file mode 100644 index 000000000..a9ac7df0a --- /dev/null +++ b/tests/UnitTests/OptionalFields/Generics.hs @@ -0,0 +1,139 @@ +{-# LANGUAGE TypeApplications #-} +{-# OPTIONS_GHC -Wno-orphans #-} + +module UnitTests.OptionalFields.Generics (omitGenerics) where + +import UnitTests.OptionalFields.Common + +------------------------------------------------------------------------------- +-- Ordinary +------------------------------------------------------------------------------- + +instance ToJSON RecordA where + toJSON = genericToJSON omittingOptions + toEncoding = genericToEncoding omittingOptions + +instance FromJSON RecordA where + parseJSON = genericParseJSON omittingOptions + +instance ToJSON RecordB where + toJSON = genericToJSON nonOmittingOptions + toEncoding = genericToEncoding nonOmittingOptions + +instance FromJSON RecordB where + parseJSON = genericParseJSON nonOmittingOptions + +instance ToJSON RecordC where + toJSON = genericToJSON defaultOptions + toEncoding = genericToEncoding defaultOptions + +instance FromJSON RecordC where + parseJSON = genericParseJSON defaultOptions + +------------------------------------------------------------------------------- +-- Higher +------------------------------------------------------------------------------- + +instance ToJSON1 HRecordA where + liftToJSON = genericLiftToJSON omittingOptions + liftToEncoding = genericLiftToEncoding omittingOptions + +instance FromJSON1 HRecordA where + liftParseJSON = genericLiftParseJSON omittingOptions + +instance ToJSON a => ToJSON (HRecordA a) where + toJSON = toJSON1 + toEncoding = toEncoding1 + +instance FromJSON a => FromJSON (HRecordA a) where + parseJSON = parseJSON1 + + +instance ToJSON1 HRecordB where + liftToJSON = genericLiftToJSON nonOmittingOptions + liftToEncoding = genericLiftToEncoding nonOmittingOptions + +instance FromJSON1 HRecordB where + liftParseJSON = genericLiftParseJSON nonOmittingOptions + +instance ToJSON a => ToJSON (HRecordB a) where + toJSON = toJSON1 + toEncoding = toEncoding1 + +instance FromJSON a => FromJSON (HRecordB a) where + parseJSON = parseJSON1 + + +instance ToJSON1 HRecordC where + liftToJSON = genericLiftToJSON defaultOptions + liftToEncoding = genericLiftToEncoding defaultOptions + +instance FromJSON1 HRecordC where + liftParseJSON = genericLiftParseJSON defaultOptions + +instance ToJSON a => ToJSON (HRecordC a) where + toJSON = toJSON1 + toEncoding = toEncoding1 + +instance FromJSON a => FromJSON (HRecordC a) where + parseJSON = parseJSON1 + +------------------------------------------------------------------------------- +-- Tests +------------------------------------------------------------------------------- + +omitGenerics :: TestTree +omitGenerics = testGroup "Omit optional fields (Generics)" + [ testGroup "ordinary" + [ testGroup "omitNothingFields = True" + [ testCase "JSON should include non-optional value." $ encodeCase helloWorldRecA helloWorldObj + , testCase "JSON should not include optional value." $ encodeCase helloRecA helloObj + , testCase "JSON decode including non-optional value" $ decodeCase helloWorldRecA helloWorldObj + , testCase "JSON decode not including optional value" $ decodeCase helloRecA helloObj + , testCase "JSON decode including optional value" $ decodeCase helloRecA helloNullObj + , testCase "JSON decode including optional value 2" $ counterCase (Proxy @RecordA) helloNullObj2 + ] + , testGroup "omitNothingFields = False" + [ testCase "JSON should include non-optional value." $ encodeCase helloWorldRecB helloWorldObj + , testCase "JSON should include optional value." $ encodeCase helloRecB helloNullObj + , testCase "JSON decode including non-optional value" $ decodeCase helloWorldRecB helloWorldObj + , testCase "JSON decode not including optional value" $ counterCase (Proxy @RecordB) helloObj + , testCase "JSON decode including optional value" $ decodeCase helloRecB helloNullObj + , testCase "JSON decode including optional value 2" $ counterCase (Proxy @RecordB) helloNullObj2 -- fails because Default instance expects only numbers + ] + , testGroup "defaultOptions" + [ testCase "JSON should include non-optional value." $ encodeCase helloWorldRecC helloWorldObj + , testCase "JSON should include optional value." $ encodeCase helloRecC helloNullObj + , testCase "JSON decode including non-optional value" $ decodeCase helloWorldRecC helloWorldObj + , testCase "JSON decode not including optional value" $ decodeCase helloRecC helloObj + , testCase "JSON decode including optional value" $ decodeCase helloRecC helloNullObj + , testCase "JSON decode including optional value 2" $ counterCase (Proxy @RecordC) helloNullObj2 + ] + ] + , testGroup "higher" + [ testGroup "omitNothingFields = True, higher" + [ testCase "JSON should include non-optional value." $ encodeCase helloWorldHRecA helloWorldObj + , testCase "JSON should not include optional value." $ encodeCase helloHRecA helloObj + , testCase "JSON decode including non-optional value" $ decodeCase helloWorldHRecA helloWorldObj + , testCase "JSON decode not including optional value" $ decodeCase helloHRecA helloObj + , testCase "JSON decode including optional value" $ decodeCase helloHRecA helloNullObj + , testCase "JSON decode including optional value 2" $ counterCase (Proxy @HRecordA') helloNullObj2 + ] + , testGroup "omitNothingFields = False, higher" + [ testCase "JSON should include non-optional value." $ encodeCase helloWorldHRecB helloWorldObj + , testCase "JSON should include optional value." $ encodeCase helloHRecB helloNullObj + , testCase "JSON decode including non-optional value" $ decodeCase helloWorldHRecB helloWorldObj + , testCase "JSON decode not including optional value" $ counterCase (Proxy @HRecordB') helloObj + , testCase "JSON decode including optional value" $ decodeCase helloHRecB helloNullObj + , testCase "JSON decode including optional value 2" $ counterCase (Proxy @HRecordB') helloNullObj2 + ] + , testGroup "defaultOptions, higher" + [ testCase "JSON should include non-optional value." $ encodeCase helloWorldHRecC helloWorldObj + , testCase "JSON should include optional value." $ encodeCase helloHRecC helloNullObj + , testCase "JSON decode including non-optional value" $ decodeCase helloWorldHRecC helloWorldObj + , testCase "JSON decode not including optional value" $ decodeCase helloHRecC helloObj + , testCase "JSON decode including optional value" $ decodeCase helloHRecC helloNullObj + , testCase "JSON decode including optional value 2" $ counterCase (Proxy @HRecordC') helloNullObj2 + ] + ] + ] diff --git a/tests/UnitTests/OptionalFields/Manual.hs b/tests/UnitTests/OptionalFields/Manual.hs new file mode 100644 index 000000000..902c42966 --- /dev/null +++ b/tests/UnitTests/OptionalFields/Manual.hs @@ -0,0 +1,167 @@ +{-# LANGUAGE CPP #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE TypeApplications #-} +{-# OPTIONS_GHC -Wno-orphans #-} + +module UnitTests.OptionalFields.Manual (omitManual) where + +#if !MIN_VERSION_base(4,11,0) +import Data.Semigroup ((<>)) +#endif + +import UnitTests.OptionalFields.Common + +------------------------------------------------------------------------------- +-- Ordinary +------------------------------------------------------------------------------- + +-- lax +instance ToJSON RecordA where + toJSON RecordA {..} = Object $ "required" .?= required <> "optional" .?= optional <> "default_" .?= default_ + toEncoding RecordA {..} = pairs $ "required" .?= required <> "optional" .?= optional <> "default_" .?= default_ + +instance FromJSON RecordA where + parseJSON = withObject "RecordA" $ \obj -> pure RecordA + <*> obj .:!= "required" + <*> obj .:!= "optional" + <*> obj .:!= "default_" + +-- strict +instance ToJSON RecordB where + toJSON RecordB {..} = Object $ "required" .= required <> "optional" .= optional <> "default_" .= default_ + toEncoding RecordB {..} = pairs $ "required" .= required <> "optional" .= optional <> "default_" .= default_ + +instance FromJSON RecordB where + parseJSON = withObject "RecordB" $ \obj -> pure RecordB + <*> obj .: "required" + <*> obj .: "optional" + <*> obj .: "default_" + +-- default: encoding strict, decoding lax +instance ToJSON RecordC where + toJSON RecordC {..} = Object $ "required" .= required <> "optional" .= optional <> "default_" .= default_ + toEncoding RecordC {..} = pairs $ "required" .= required <> "optional" .= optional <> "default_" .= default_ + +instance FromJSON RecordC where + parseJSON = withObject "RecordC" $ \obj -> pure RecordC + <*> obj .:!= "required" + <*> obj .:!= "optional" + <*> obj .:!= "default_" + +------------------------------------------------------------------------------- +-- Higher +------------------------------------------------------------------------------- + +instance ToJSON1 HRecordA where + liftToJSON o f _ HRecordA {..} = Object $ "required" .?= required <> explicitToFieldOmit o f "optional" optional <> "default_" .?= default_ + liftToEncoding o f _ HRecordA {..} = pairs $ "required" .?= required <> explicitToFieldOmit o f "optional" optional <> "default_" .?= default_ + +instance ToJSON a => ToJSON (HRecordA a) where + toJSON = toJSON1 + toEncoding = toEncoding1 + +instance FromJSON1 HRecordA where + liftParseJSON o f _ = withObject "HRecordA" $ \obj -> pure HRecordA + <*> obj .:!= "required" + <*> explicitParseFieldOmit o f obj "optional" + <*> obj .:!= "default_" + +instance FromJSON a => FromJSON (HRecordA a) where + parseJSON = parseJSON1 + + +instance ToJSON1 HRecordB where + liftToJSON _o f _ HRecordB {..} = Object $ "required" .= required <> explicitToField f "optional" optional <> "default_" .= default_ + liftToEncoding _o f _ HRecordB {..} = pairs $ "required" .= required <> explicitToField f "optional" optional <> "default_" .= default_ + +instance ToJSON a => ToJSON (HRecordB a) where + toJSON = toJSON1 + toEncoding = toEncoding1 + +instance FromJSON1 HRecordB where + liftParseJSON _o f _ = withObject "HRecordB" $ \obj -> pure HRecordB + <*> obj .: "required" + <*> explicitParseField f obj "optional" + <*> obj .: "default_" + +instance FromJSON a => FromJSON (HRecordB a) where + parseJSON = parseJSON1 + + +instance ToJSON1 HRecordC where + liftToJSON _o f _ HRecordC {..} = Object $ "required" .= required <> explicitToField f "optional" optional <> "default_" .= default_ + liftToEncoding _o f _ HRecordC {..} = pairs $ "required" .= required <> explicitToField f "optional" optional <> "default_" .= default_ + +instance ToJSON a => ToJSON (HRecordC a) where + toJSON = toJSON1 + toEncoding = toEncoding1 + +instance FromJSON1 HRecordC where + liftParseJSON o f _ = withObject "HRecordC" $ \obj -> pure HRecordC + <*> obj .:!= "required" + <*> explicitParseFieldOmit o f obj "optional" + <*> obj .:!= "default_" + +instance FromJSON a => FromJSON (HRecordC a) where + parseJSON = parseJSON1 + +------------------------------------------------------------------------------- +-- Tests +------------------------------------------------------------------------------- + +omitManual :: TestTree +omitManual = testGroup "Omit optional fields (Manual)" + [ testGroup "ordinary" + [ testGroup "omitNothingFields = True" + [ testCase "JSON should include non-optional value." $ encodeCase helloWorldRecA helloWorldObj + , testCase "JSON should not include optional value." $ encodeCase helloRecA helloObj + , testCase "JSON decode including non-optional value" $ decodeCase helloWorldRecA helloWorldObj + , testCase "JSON decode not including optional value" $ decodeCase helloRecA helloObj + , testCase "JSON decode including optional value" $ decodeCase helloRecA helloNullObj + , testCase "JSON decode including optional value 2" $ counterCase (Proxy @RecordA) helloNullObj2 + ] + , testGroup "omitNothingFields = False" + [ testCase "JSON should include non-optional value." $ encodeCase helloWorldRecB helloWorldObj + , testCase "JSON should include optional value." $ encodeCase helloRecB helloNullObj + , testCase "JSON decode including non-optional value" $ decodeCase helloWorldRecB helloWorldObj + , testCase "JSON decode not including optional value" $ counterCase (Proxy @RecordB) helloObj + , testCase "JSON decode including optional value" $ decodeCase helloRecB helloNullObj + , testCase "JSON decode including optional value 2" $ counterCase (Proxy @RecordB) helloNullObj2 -- fails because Default instance expects only numbers + ] + , testGroup "defaultOptions" + [ testCase "JSON should include non-optional value." $ encodeCase helloWorldRecC helloWorldObj + , testCase "JSON should include optional value." $ encodeCase helloRecC helloNullObj + , testCase "JSON decode including non-optional value" $ decodeCase helloWorldRecC helloWorldObj + , testCase "JSON decode not including optional value" $ decodeCase helloRecC helloObj + , testCase "JSON decode including optional value" $ decodeCase helloRecC helloNullObj + , testCase "JSON decode including optional value 2" $ counterCase (Proxy @RecordC) helloNullObj2 + ] + ] + , testGroup "higher" + [ testGroup "omitNothingFields = True, higher" + [ testCase "JSON should include non-optional value." $ encodeCase helloWorldHRecA helloWorldObj + , testCase "JSON should not include optional value." $ encodeCase helloHRecA helloObj + , testCase "JSON decode including non-optional value" $ decodeCase helloWorldHRecA helloWorldObj + , testCase "JSON decode not including optional value" $ decodeCase helloHRecA helloObj + , testCase "JSON decode including optional value" $ decodeCase helloHRecA helloNullObj + , testCase "JSON decode including optional value 2" $ counterCase (Proxy @HRecordA') helloNullObj2 + ] + , testGroup "omitNothingFields = False, higher" + [ testCase "JSON should include non-optional value." $ encodeCase helloWorldHRecB helloWorldObj + , testCase "JSON should include optional value." $ encodeCase helloHRecB helloNullObj + , testCase "JSON decode including non-optional value" $ decodeCase helloWorldHRecB helloWorldObj + , testCase "JSON decode not including optional value" $ counterCase (Proxy @HRecordB') helloObj + , testCase "JSON decode including optional value" $ decodeCase helloHRecB helloNullObj + , testCase "JSON decode including optional value 2" $ counterCase (Proxy @HRecordB') helloNullObj2 + ] + , testGroup "defaultOptions, higher" + [ testCase "JSON should include non-optional value." $ encodeCase helloWorldHRecC helloWorldObj + , testCase "JSON should include optional value." $ encodeCase helloHRecC helloNullObj + , testCase "JSON decode including non-optional value" $ decodeCase helloWorldHRecC helloWorldObj + , testCase "JSON decode not including optional value" $ decodeCase helloHRecC helloObj + , testCase "JSON decode including optional value" $ decodeCase helloHRecC helloNullObj + , testCase "JSON decode including optional value 2" $ counterCase (Proxy @HRecordC') helloNullObj2 + ] + ] + ] diff --git a/tests/UnitTests/OptionalFields/TH.hs b/tests/UnitTests/OptionalFields/TH.hs new file mode 100644 index 000000000..fd27e63fc --- /dev/null +++ b/tests/UnitTests/OptionalFields/TH.hs @@ -0,0 +1,95 @@ +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE TypeApplications #-} +{-# OPTIONS_GHC -Wno-orphans #-} +-- {-# OPTIONS_GHC -ddump-splices #-} + +module UnitTests.OptionalFields.TH (omitTH) where + +import UnitTests.OptionalFields.Common + +$(deriveJSON omittingOptions ''RecordA) +$(deriveJSON nonOmittingOptions ''RecordB) +$(deriveJSON defaultOptions ''RecordC) +$(deriveJSON1 omittingOptions ''HRecordA) +$(deriveJSON1 nonOmittingOptions ''HRecordB) +$(deriveJSON1 defaultOptions ''HRecordC) + + +instance ToJSON a => ToJSON (HRecordA a) where + toJSON = toJSON1 + toEncoding = toEncoding1 + +instance FromJSON a => FromJSON (HRecordA a) where + parseJSON = parseJSON1 + +instance ToJSON a => ToJSON (HRecordB a) where + toJSON = toJSON1 + toEncoding = toEncoding1 + +instance FromJSON a => FromJSON (HRecordB a) where + parseJSON = parseJSON1 + +instance ToJSON a => ToJSON (HRecordC a) where + toJSON = toJSON1 + toEncoding = toEncoding1 + +instance FromJSON a => FromJSON (HRecordC a) where + parseJSON = parseJSON1 + + + +omitTH :: TestTree +omitTH = testGroup "Omit optional fields (TH)" + [ testGroup "ordinary" + [ testGroup "omitNothingFields = True" + [ testCase "JSON should include non-optional value." $ encodeCase helloWorldRecA helloWorldObj + , testCase "JSON should not include optional value." $ encodeCase helloRecA helloObj + , testCase "JSON decode including non-optional value" $ decodeCase helloWorldRecA helloWorldObj + , testCase "JSON decode not including optional value" $ decodeCase helloRecA helloObj + , testCase "JSON decode including optional value" $ decodeCase helloRecA helloNullObj + , testCase "JSON decode including optional value 2" $ counterCase (Proxy @RecordA) helloNullObj2 + ] + , testGroup "omitNothingFields = False" + [ testCase "JSON should include non-optional value." $ encodeCase helloWorldRecB helloWorldObj + , testCase "JSON should include optional value." $ encodeCase helloRecB helloNullObj + , testCase "JSON decode including non-optional value" $ decodeCase helloWorldRecB helloWorldObj + , testCase "JSON decode not including optional value" $ counterCase (Proxy @RecordB) helloObj + , testCase "JSON decode including optional value" $ decodeCase helloRecB helloNullObj + , testCase "JSON decode including optional value 2" $ counterCase (Proxy @RecordB) helloNullObj2 -- fails because Default instance expects only numbers + ] + , testGroup "defaultOptions" + [ testCase "JSON should include non-optional value." $ encodeCase helloWorldRecC helloWorldObj + , testCase "JSON should include optional value." $ encodeCase helloRecC helloNullObj + , testCase "JSON decode including non-optional value" $ decodeCase helloWorldRecC helloWorldObj + , testCase "JSON decode not including optional value" $ decodeCase helloRecC helloObj + , testCase "JSON decode including optional value" $ decodeCase helloRecC helloNullObj + , testCase "JSON decode including optional value 2" $ counterCase (Proxy @RecordC) helloNullObj2 + ] + ] + , testGroup "higher" + [ testGroup "omitNothingFields = True, higher" + [ testCase "JSON should include non-optional value." $ encodeCase helloWorldHRecA helloWorldObj + , testCase "JSON should not include optional value." $ encodeCase helloHRecA helloObj + , testCase "JSON decode including non-optional value" $ decodeCase helloWorldHRecA helloWorldObj + , testCase "JSON decode not including optional value" $ decodeCase helloHRecA helloObj + , testCase "JSON decode including optional value" $ decodeCase helloHRecA helloNullObj + , testCase "JSON decode including optional value 2" $ counterCase (Proxy @HRecordA') helloNullObj2 + ] + , testGroup "omitNothingFields = False, higher" + [ testCase "JSON should include non-optional value." $ encodeCase helloWorldHRecB helloWorldObj + , testCase "JSON should include optional value." $ encodeCase helloHRecB helloNullObj + , testCase "JSON decode including non-optional value" $ decodeCase helloWorldHRecB helloWorldObj + , testCase "JSON decode not including optional value" $ counterCase (Proxy @HRecordB') helloObj + , testCase "JSON decode including optional value" $ decodeCase helloHRecB helloNullObj + , testCase "JSON decode including optional value 2" $ counterCase (Proxy @HRecordB') helloNullObj2 + ] + , testGroup "defaultOptions, higher" + [ testCase "JSON should include non-optional value." $ encodeCase helloWorldHRecC helloWorldObj + , testCase "JSON should include optional value." $ encodeCase helloHRecC helloNullObj + , testCase "JSON decode including non-optional value" $ decodeCase helloWorldHRecC helloWorldObj + , testCase "JSON decode not including optional value" $ decodeCase helloHRecC helloObj + , testCase "JSON decode including optional value" $ decodeCase helloHRecC helloNullObj + , testCase "JSON decode including optional value 2" $ counterCase (Proxy @HRecordC') helloNullObj2 + ] + ] + ]