Skip to content

Commit 5c3a18e

Browse files
committed
Add combinators for using omit* stuff in manually written instances
- Add Manual tests - Cleanup OptionalFields.Common - More tests: TH/Generics FromJSON doesn't work - Fix TH - Fix Generics - ToJSON1/2 and FromJSON1/2
1 parent 97b7bd8 commit 5c3a18e

15 files changed

+1005
-523
lines changed

aeson.cabal

+1
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ test-suite aeson-tests
171171
UnitTests.OptionalFields
172172
UnitTests.OptionalFields.Common
173173
UnitTests.OptionalFields.Generics
174+
UnitTests.OptionalFields.Manual
174175
UnitTests.OptionalFields.TH
175176

176177
build-depends:

src/Data/Aeson.hs

+6
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ module Data.Aeson
7676
, fromJSON
7777
, ToJSON(..)
7878
, KeyValue(..)
79+
, KeyValueOmit(..)
7980
, (<?>)
8081
, JSONPath
8182
-- ** Keys for maps
@@ -91,14 +92,18 @@ module Data.Aeson
9192
-- ** Liftings to unary and binary type constructors
9293
, FromJSON1(..)
9394
, parseJSON1
95+
, omittedField1
9496
, FromJSON2(..)
9597
, parseJSON2
98+
, omittedField2
9699
, ToJSON1(..)
97100
, toJSON1
98101
, toEncoding1
102+
, omitField1
99103
, ToJSON2(..)
100104
, toJSON2
101105
, toEncoding2
106+
, omitField2
102107
-- ** Generic JSON classes and options
103108
, GFromJSON
104109
, FromArgs
@@ -151,6 +156,7 @@ module Data.Aeson
151156
, (.:?)
152157
, (.:!)
153158
, (.!=)
159+
, (.:?=)
154160
, object
155161
-- * Parsing
156162
, parseIndexedJSON

src/Data/Aeson/TH.hs

+75-41
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ import Data.Aeson.Key (Key)
124124
import qualified Data.Aeson.Key as Key
125125
import qualified Data.Aeson.KeyMap as KM
126126
import Data.Foldable (foldr')
127-
import Data.List (genericLength, intercalate, partition, union)
127+
import Data.List (genericLength, intercalate, union)
128128
import Data.List.NonEmpty ((<|), NonEmpty((:|)))
129129
import Data.Map (Map)
130130
import qualified Data.Monoid as Monoid
@@ -321,10 +321,11 @@ consToValue _ _ _ _ [] =
321321

322322
consToValue target jc opts instTys cons = autoletE liftSBS $ \letInsert -> do
323323
value <- newName "value"
324+
os <- newNameList "_o" $ arityInt jc
324325
tjs <- newNameList "_tj" $ arityInt jc
325326
tjls <- newNameList "_tjl" $ arityInt jc
326-
let zippedTJs = zip tjs tjls
327-
interleavedTJs = interleave tjs tjls
327+
let zippedTJs = zip3 os tjs tjls
328+
interleavedTJs = flatten3 zippedTJs
328329
lastTyVars = map varTToName $ drop (length instTys - arityInt jc) instTys
329330
tvMap = M.fromList $ zip lastTyVars zippedTJs
330331
lamE (map varP $ interleavedTJs ++ [value]) $
@@ -461,8 +462,10 @@ argsToValue letInsert target jc tvMap opts multiCons
461462
toPair (arg, argTy, fld) =
462463
let fieldName = fieldLabel opts fld
463464
toValue = dispatchToJSON target jc conName tvMap argTy
465+
466+
omitFn :: Q Exp
464467
omitFn
465-
| omitNothingFields opts = [| omitField |]
468+
| omitNothingFields opts = dispatchOmitField jc conName tvMap argTy
466469
| otherwise = [| const False |]
467470
in
468471
[| \f x arg' -> bool x mempty (f arg') |]
@@ -653,10 +656,11 @@ consFromJSON _ _ _ _ [] =
653656

654657
consFromJSON jc tName opts instTys cons = do
655658
value <- newName "value"
659+
os <- newNameList "_o" $ arityInt jc
656660
pjs <- newNameList "_pj" $ arityInt jc
657661
pjls <- newNameList "_pjl" $ arityInt jc
658-
let zippedPJs = zip pjs pjls
659-
interleavedPJs = interleave pjs pjls
662+
let zippedPJs = zip3 os pjs pjls
663+
interleavedPJs = flatten3 zippedPJs
660664
lastTyVars = map varTToName $ drop (length instTys - arityInt jc) instTys
661665
tvMap = M.fromList $ zip lastTyVars zippedPJs
662666
lamE (map varP $ interleavedPJs ++ [value]) $ lamExpr value tvMap
@@ -921,9 +925,11 @@ parseRecord jc tvMap argTys opts tName conName fields obj inTaggedObject =
921925
(infixApp (conE conName) [|(<$>)|] x)
922926
xs
923927
where
924-
defVal = case jc of
925-
JSONClass From Arity0 -> [|omittedField|]
926-
_ -> [|Nothing|]
928+
lookupField :: Type -> Q Exp
929+
lookupField argTy
930+
| omitNothingFields opts = [| lookupFieldOmit |] `appE` dispatchOmittedField jc conName tvMap argTy
931+
| otherwise = [| lookupFieldNoOmit |]
932+
927933
tagFieldNameAppender =
928934
if inTaggedObject then (tagFieldName (sumEncoding opts) :) else id
929935
knownFields = appE [|KM.fromList|] $ listE $
@@ -940,8 +946,7 @@ parseRecord jc tvMap argTys opts tName conName fields obj inTaggedObject =
940946
(appE [|show|] (varE unknownFields)))
941947
[]
942948
]
943-
x:xs = [ [|lookupField|]
944-
`appE` defVal
949+
x:xs = [ lookupField argTy
945950
`appE` dispatchParseJSON jc conName tvMap argTy
946951
`appE` litE (stringL $ show tName)
947952
`appE` litE (stringL $ constructorTagModifier opts $ nameBase conName)
@@ -1109,16 +1114,21 @@ parseTypeMismatch tName conName expected actual =
11091114
, actual
11101115
]
11111116

1112-
lookupField :: Maybe a -> (Value -> Parser a) -> String -> String
1113-
-> Object -> Key -> Parser a
1114-
lookupField maybeDefault pj tName rec obj key =
1117+
lookupFieldOmit :: Maybe a -> (Value -> Parser a) -> String -> String -> Object -> Key -> Parser a
1118+
lookupFieldOmit maybeDefault pj tName rec obj key =
11151119
case KM.lookup key obj of
11161120
Nothing ->
11171121
case maybeDefault of
11181122
Nothing -> unknownFieldFail tName rec (Key.toString key)
11191123
Just x -> pure x
11201124
Just v -> pj v <?> Key key
11211125

1126+
lookupFieldNoOmit :: (Value -> Parser a) -> String -> String -> Object -> Key -> Parser a
1127+
lookupFieldNoOmit pj tName rec obj key =
1128+
case KM.lookup key obj of
1129+
Nothing -> unknownFieldFail tName rec (Key.toString key)
1130+
Just v -> pj v <?> Key key
1131+
11221132
unknownFieldFail :: String -> String -> String -> Parser fail
11231133
unknownFieldFail tName rec key =
11241134
fail $ printf "When parsing the record %s of type %s the key %s was not present."
@@ -1245,20 +1255,26 @@ mkFunCommon consFun jc opts name = do
12451255
!_ <- buildTypeInstance parentName jc ctxt instTys variant
12461256
consFun jc parentName opts instTys cons
12471257

1258+
data FunArg = Omit | Single | Plural
1259+
12481260
dispatchFunByType :: JSONClass
12491261
-> JSONFun
12501262
-> Name
12511263
-> TyVarMap
1252-
-> Bool -- True if we are using the function argument that works
1253-
-- on lists (e.g., [a] -> Value). False is we are using
1254-
-- the function argument that works on single values
1255-
-- (e.g., a -> Value).
1264+
-> FunArg -- Plural if we are using the function argument that works
1265+
-- on lists (e.g., [a] -> Value). Single is we are using
1266+
-- the function argument that works on single values
1267+
-- (e.g., a -> Value). Omit if we use it to check omission
1268+
-- (e.g. a -> Bool)
12561269
-> Type
12571270
-> Q Exp
12581271
dispatchFunByType _ jf _ tvMap list (VarT tyName) =
12591272
varE $ case M.lookup tyName tvMap of
1260-
Just (tfjExp, tfjlExp) -> if list then tfjlExp else tfjExp
1261-
Nothing -> jsonFunValOrListName list jf Arity0
1273+
Just (tfjoExp, tfjExp, tfjlExp) -> case list of
1274+
Omit -> tfjoExp
1275+
Single -> tfjExp
1276+
Plural -> tfjlExp
1277+
Nothing -> jsonFunValOrListName list jf Arity0
12621278
dispatchFunByType jc jf conName tvMap list (SigT ty _) =
12631279
dispatchFunByType jc jf conName tvMap list ty
12641280
dispatchFunByType jc jf conName tvMap list (ForallT _ _ ty) =
@@ -1283,18 +1299,21 @@ dispatchFunByType jc jf conName tvMap list ty = do
12831299
else if any (`mentionsName` tyVarNames) rhsArgs
12841300
then appsE $ varE (jsonFunValOrListName list jf $ toEnum numLastArgs)
12851301
: zipWith (dispatchFunByType jc jf conName tvMap)
1286-
(cycle [False,True])
1287-
(interleave rhsArgs rhsArgs)
1302+
(cycle [Omit,Single,Plural])
1303+
(triple rhsArgs)
12881304
else varE $ jsonFunValOrListName list jf Arity0
12891305

1290-
dispatchToJSON
1291-
:: ToJSONFun -> JSONClass -> Name -> TyVarMap -> Type -> Q Exp
1292-
dispatchToJSON target jc n tvMap =
1293-
dispatchFunByType jc (targetToJSONFun target) n tvMap False
1306+
dispatchToJSON :: ToJSONFun -> JSONClass -> Name -> TyVarMap -> Type -> Q Exp
1307+
dispatchToJSON target jc n tvMap = dispatchFunByType jc (targetToJSONFun target) n tvMap Single
1308+
1309+
dispatchOmitField :: JSONClass -> Name -> TyVarMap -> Type -> Q Exp
1310+
dispatchOmitField jc n tvMap = dispatchFunByType jc ToJSON n tvMap Omit
1311+
1312+
dispatchParseJSON :: JSONClass -> Name -> TyVarMap -> Type -> Q Exp
1313+
dispatchParseJSON jc n tvMap = dispatchFunByType jc ParseJSON n tvMap Single
12941314

1295-
dispatchParseJSON
1296-
:: JSONClass -> Name -> TyVarMap -> Type -> Q Exp
1297-
dispatchParseJSON jc n tvMap = dispatchFunByType jc ParseJSON n tvMap False
1315+
dispatchOmittedField :: JSONClass -> Name -> TyVarMap -> Type -> Q Exp
1316+
dispatchOmittedField jc n tvMap = dispatchFunByType jc ParseJSON n tvMap Omit
12981317

12991318
--------------------------------------------------------------------------------
13001319
-- Utility functions
@@ -1565,13 +1584,14 @@ Both.
15651584
-- A mapping of type variable Names to their encoding/decoding function Names.
15661585
-- For example, in a ToJSON2 declaration, a TyVarMap might look like
15671586
--
1568-
-- { a ~> (tj1, tjl1)
1569-
-- , b ~> (tj2, tjl2) }
1587+
-- { a ~> (o1, tj1, tjl1)
1588+
-- , b ~> (o2, tj2, tjl2) }
15701589
--
1571-
-- where a and b are the last two type variables of the datatype, tj1 and tjl1 are
1572-
-- the function arguments of types (a -> Value) and ([a] -> Value), and tj2 and tjl2
1573-
-- are the function arguments of types (b -> Value) and ([b] -> Value).
1574-
type TyVarMap = Map Name (Name, Name)
1590+
-- where a and b are the last two type variables of the datatype,
1591+
-- o1 and o2 are function argument of types (a -> Bool),
1592+
-- tj1 and tjl1 are the function arguments of types (a -> Value)
1593+
-- and ([a] -> Value), and tj2 and tjl2 are the function arguments of types (b -> Value) and ([b] -> Value).
1594+
type TyVarMap = Map Name (Name, Name, Name)
15751595

15761596
-- | Returns True if a Type has kind *.
15771597
hasKindStar :: Type -> Bool
@@ -1616,9 +1636,11 @@ varTToNameMaybe _ = Nothing
16161636
varTToName :: Type -> Name
16171637
varTToName = fromMaybe (error "Not a type variable!") . varTToNameMaybe
16181638

1619-
interleave :: [a] -> [a] -> [a]
1620-
interleave (a1:a1s) (a2:a2s) = a1:a2:interleave a1s a2s
1621-
interleave _ _ = []
1639+
flatten3 :: [(a,a,a)] -> [a]
1640+
flatten3 = foldr (\(a,b,c) xs -> a:b:c:xs) []
1641+
1642+
triple :: [a] -> [a]
1643+
triple = foldr (\x xs -> x:x:x:xs) []
16221644

16231645
-- | Fully applies a type constructor to its type variables.
16241646
applyTyCon :: Name -> [Type] -> Type
@@ -1909,6 +1931,17 @@ jsonClassName (JSONClass From Arity0) = ''FromJSON
19091931
jsonClassName (JSONClass From Arity1) = ''FromJSON1
19101932
jsonClassName (JSONClass From Arity2) = ''FromJSON2
19111933

1934+
jsonFunOmitName :: JSONFun -> Arity -> Name
1935+
jsonFunOmitName ToJSON Arity0 = 'omitField
1936+
jsonFunOmitName ToJSON Arity1 = 'liftOmitField
1937+
jsonFunOmitName ToJSON Arity2 = 'liftOmitField2
1938+
jsonFunOmitName ToEncoding Arity0 = 'omitField
1939+
jsonFunOmitName ToEncoding Arity1 = 'liftOmitField
1940+
jsonFunOmitName ToEncoding Arity2 = 'liftOmitField2
1941+
jsonFunOmitName ParseJSON Arity0 = 'omittedField
1942+
jsonFunOmitName ParseJSON Arity1 = 'liftOmittedField
1943+
jsonFunOmitName ParseJSON Arity2 = 'liftOmittedField2
1944+
19121945
jsonFunValName :: JSONFun -> Arity -> Name
19131946
jsonFunValName ToJSON Arity0 = 'toJSON
19141947
jsonFunValName ToJSON Arity1 = 'liftToJSON
@@ -1931,10 +1964,11 @@ jsonFunListName ParseJSON Arity0 = 'parseJSONList
19311964
jsonFunListName ParseJSON Arity1 = 'liftParseJSONList
19321965
jsonFunListName ParseJSON Arity2 = 'liftParseJSONList2
19331966

1934-
jsonFunValOrListName :: Bool -- e.g., toJSONList if True, toJSON if False
1967+
jsonFunValOrListName :: FunArg -- e.g., toJSONList if True, toJSON if False
19351968
-> JSONFun -> Arity -> Name
1936-
jsonFunValOrListName False = jsonFunValName
1937-
jsonFunValOrListName True = jsonFunListName
1969+
jsonFunValOrListName Omit = jsonFunOmitName
1970+
jsonFunValOrListName Single = jsonFunValName
1971+
jsonFunValOrListName Plural = jsonFunListName
19381972

19391973
arityInt :: JSONClass -> Int
19401974
arityInt = fromEnum . arity

src/Data/Aeson/Types.hs

+8
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ module Data.Aeson.Types
5050
-- ** Encoding
5151
, ToJSON(..)
5252
, KeyValue(..)
53+
, KeyValueOmit(..)
5354

5455
-- ** Keys for maps
5556
, ToJSONKey(..)
@@ -72,14 +73,18 @@ module Data.Aeson.Types
7273
-- ** Liftings to unary and binary type constructors
7374
, FromJSON1(..)
7475
, parseJSON1
76+
, omittedField1
7577
, FromJSON2(..)
7678
, parseJSON2
79+
, omittedField2
7780
, ToJSON1(..)
7881
, toJSON1
7982
, toEncoding1
83+
, omitField1
8084
, ToJSON2(..)
8185
, toJSON2
8286
, toEncoding2
87+
, omitField2
8388

8489
-- ** Generic JSON classes
8590
, GFromJSON
@@ -111,13 +116,16 @@ module Data.Aeson.Types
111116
, (.:?)
112117
, (.:!)
113118
, (.!=)
119+
, (.:?=)
114120
, object
115121
, parseField
116122
, parseFieldMaybe
117123
, parseFieldMaybe'
124+
, parseFieldOmit
118125
, explicitParseField
119126
, explicitParseFieldMaybe
120127
, explicitParseFieldMaybe'
128+
, explicitParseFieldOmit
121129

122130
, listEncoding
123131
, listValue

src/Data/Aeson/Types/Class.hs

+8
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,18 @@ module Data.Aeson.Types.Class
2626
-- * Liftings to unary and binary type constructors
2727
, FromJSON1(..)
2828
, parseJSON1
29+
, omittedField1
2930
, FromJSON2(..)
3031
, parseJSON2
32+
, omittedField2
3133
, ToJSON1(..)
3234
, toJSON1
3335
, toEncoding1
36+
, omitField1
3437
, ToJSON2(..)
3538
, toJSON2
3639
, toEncoding2
40+
, omitField2
3741
-- * Generic JSON classes
3842
, GFromJSON(..)
3943
, FromArgs(..)
@@ -67,6 +71,7 @@ module Data.Aeson.Types.Class
6771
, genericFromJSONKey
6872
-- * Object key-value pairs
6973
, KeyValue(..)
74+
, KeyValueOmit(..)
7075

7176
-- * List functions
7277
, listEncoding
@@ -89,14 +94,17 @@ module Data.Aeson.Types.Class
8994
, parseField
9095
, parseFieldMaybe
9196
, parseFieldMaybe'
97+
, parseFieldOmit
9298
, explicitParseField
9399
, explicitParseFieldMaybe
94100
, explicitParseFieldMaybe'
101+
, explicitParseFieldOmit
95102
-- ** Operators
96103
, (.:)
97104
, (.:?)
98105
, (.:!)
99106
, (.!=)
107+
, (.:?=)
100108
) where
101109

102110

0 commit comments

Comments
 (0)