Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 0e89d96

Browse files
authoredOct 5, 2022
Support Function values for JsonKey.default value (google#1216)
Update docs in json_annotation Prepare to release json_serializable v6.5.0 Closes google#1185
1 parent 7d3bf20 commit 0e89d96

16 files changed

+189
-26
lines changed
 

‎json_annotation/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 4.7.1-dev
2+
3+
- Update `JsonKey` documentation to align with new features in
4+
`package:json_serializable`.
5+
16
## 4.7.0
27

38
- Added `JsonEnum.valueField` which allows specifying a field in an

‎json_annotation/lib/src/json_key.dart

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ import 'json_serializable.dart';
1212
class JsonKey {
1313
/// The value to use if the source JSON does not contain this key or if the
1414
/// value is `null`.
15+
///
16+
/// Also supported: a top-level or static [Function] or a constructor with no
17+
/// required parameters and a return type compatible with the field being
18+
/// assigned.
1519
final Object? defaultValue;
1620

1721
/// If `true`, generated code will throw a [DisallowedNullValueException] if
@@ -30,8 +34,9 @@ class JsonKey {
3034
/// A [Function] to use when decoding the associated JSON value to the
3135
/// annotated field.
3236
///
33-
/// Must be a top-level or static [Function] that takes one argument mapping
34-
/// a JSON literal to a value compatible with the type of the annotated field.
37+
/// Must be a top-level or static [Function] or a constructor that accepts one
38+
/// positional argument mapping a JSON literal to a value compatible with the
39+
/// type of the annotated field.
3540
///
3641
/// When creating a class that supports both `toJson` and `fromJson`
3742
/// (the default), you should also set [toJson] if you set [fromJson].
@@ -94,8 +99,9 @@ class JsonKey {
9499

95100
/// A [Function] to use when encoding the annotated field to JSON.
96101
///
97-
/// Must be a top-level or static [Function] with one parameter compatible
98-
/// with the field being serialized that returns a JSON-compatible value.
102+
/// Must be a top-level or static [Function] or a constructor that accepts one
103+
/// positional argument compatible with the field being serialized that
104+
/// returns a JSON-compatible value.
99105
///
100106
/// When creating a class that supports both `toJson` and `fromJson`
101107
/// (the default), you should also set [fromJson] if you set [toJson].

‎json_annotation/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: json_annotation
2-
version: 4.7.0
2+
version: 4.7.1-dev
33
description: >-
44
Classes and helper functions that support JSON code generation via the
55
`json_serializable` package.

‎json_serializable/CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1-
## 6.4.2-dev
1+
## 6.5.0
22

33
- Allow constructors to be passed to `JsonKey` parameters that support
44
`Function` types.
5+
- Accept `Function` values for `JsonKey.defaultValue`. The provided
6+
`Function` will be invoked for the default value if the target JSON element is
7+
missing or `null`.
58

69
## 6.4.1
710

‎json_serializable/lib/src/json_key_utils.dart

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,9 @@ KeyConfig _from(FieldElement element, ClassConfig classAnnotation) {
5858
} else if (reader.isType) {
5959
badType = 'Type';
6060
} else if (dartObject.type is FunctionType) {
61-
// TODO: Support calling function for the default value?
61+
// Function types at the "root" are already handled. If they occur
62+
// here, it's because the function is nested instead of a collection
63+
// literal, which is NOT supported!
6264
badType = 'Function';
6365
} else if (!reader.isLiteral) {
6466
badType = dartObject.type!.element2!.name;
@@ -126,11 +128,32 @@ KeyConfig _from(FieldElement element, ClassConfig classAnnotation) {
126128
/// [fieldName] is not an `enum` value.
127129
String? createAnnotationValue(String fieldName, {bool mustBeEnum = false}) {
128130
final annotationValue = obj.read(fieldName);
129-
late final DartType annotationType;
130131

131-
final enumFields = annotationValue.isNull
132-
? null
133-
: iterateEnumFields(annotationType = annotationValue.objectValue.type!);
132+
if (annotationValue.isNull) {
133+
return null;
134+
}
135+
136+
final objectValue = annotationValue.objectValue;
137+
final annotationType = objectValue.type!;
138+
139+
if (annotationType is FunctionType) {
140+
// TODO: we could be a LOT more careful here, checking the return type
141+
// and the number of parameters. BUT! If any of those things are wrong
142+
// the generated code will be invalid, so skipping until we're bored
143+
// later
144+
145+
final functionValue = objectValue.toFunctionValue()!;
146+
147+
final invokeConst =
148+
functionValue is ConstructorElement && functionValue.isConst
149+
? 'const '
150+
: '';
151+
152+
return '$invokeConst${functionValue.qualifiedName}()';
153+
}
154+
155+
final enumFields = iterateEnumFields(annotationType);
156+
134157
if (enumFields != null) {
135158
if (mustBeEnum) {
136159
late DartType targetEnumType;
@@ -170,15 +193,12 @@ KeyConfig _from(FieldElement element, ClassConfig classAnnotation) {
170193
final enumValueNames =
171194
enumFields.map((p) => p.name).toList(growable: false);
172195

173-
final enumValueName = enumValueForDartObject<String>(
174-
annotationValue.objectValue, enumValueNames, (n) => n);
196+
final enumValueName =
197+
enumValueForDartObject<String>(objectValue, enumValueNames, (n) => n);
175198

176-
return '${annotationType.element2!.name}'
177-
'.$enumValueName';
199+
return '${annotationType.element2!.name}.$enumValueName';
178200
} else {
179-
final defaultValueLiteral = annotationValue.isNull
180-
? null
181-
: literalForObject(fieldName, annotationValue.objectValue, []);
201+
final defaultValueLiteral = literalForObject(fieldName, objectValue, []);
182202
if (defaultValueLiteral == null) {
183203
return null;
184204
}

‎json_serializable/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: json_serializable
2-
version: 6.4.2-dev
2+
version: 6.5.0
33
description: >-
44
Automatically generate code for converting to and from JSON by annotating
55
Dart classes.

‎json_serializable/test/default_value/default_value.dart

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ import 'default_value_interface.dart'
1313
ConstClass,
1414
ConstClassConverter,
1515
constClassFromJson,
16-
constClassToJson;
16+
constClassToJson,
17+
intDefaultValueFunction;
1718

1819
part 'default_value.g.dart';
1920

@@ -72,6 +73,15 @@ class DefaultValue implements dvi.DefaultValue {
7273
@JsonKey(fromJson: constClassFromJson, toJson: constClassToJson)
7374
ConstClass valueFromFunction;
7475

76+
@JsonKey(defaultValue: intDefaultValueFunction)
77+
int intDefaultValueFromFunction;
78+
79+
@JsonKey(defaultValue: ConstClass.new)
80+
ConstClass valueFromDefaultValueDefaultConstructor;
81+
82+
@JsonKey(defaultValue: ConstClass.easy)
83+
ConstClass valueFromDefaultValueNamedConstructor;
84+
7585
DefaultValue(
7686
this.fieldBool,
7787
this.fieldString,
@@ -89,6 +99,9 @@ class DefaultValue implements dvi.DefaultValue {
8999
this.constClass = const ConstClass('value'),
90100
this.valueFromConverter = const ConstClass('value'),
91101
this.valueFromFunction = const ConstClass('value'),
102+
required this.intDefaultValueFromFunction,
103+
required this.valueFromDefaultValueDefaultConstructor,
104+
required this.valueFromDefaultValueNamedConstructor,
92105
});
93106

94107
factory DefaultValue.fromJson(Map<String, dynamic> json) =>

‎json_serializable/test/default_value/default_value.g.dart

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎json_serializable/test/default_value/default_value.g_any_map__checked.dart

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ import 'default_value_interface.dart'
1313
ConstClass,
1414
ConstClassConverter,
1515
constClassFromJson,
16-
constClassToJson;
16+
constClassToJson,
17+
intDefaultValueFunction;
1718

1819
part 'default_value.g_any_map__checked.g.dart';
1920

@@ -75,6 +76,15 @@ class DefaultValue implements dvi.DefaultValue {
7576
@JsonKey(fromJson: constClassFromJson, toJson: constClassToJson)
7677
ConstClass valueFromFunction;
7778

79+
@JsonKey(defaultValue: intDefaultValueFunction)
80+
int intDefaultValueFromFunction;
81+
82+
@JsonKey(defaultValue: ConstClass.new)
83+
ConstClass valueFromDefaultValueDefaultConstructor;
84+
85+
@JsonKey(defaultValue: ConstClass.easy)
86+
ConstClass valueFromDefaultValueNamedConstructor;
87+
7888
DefaultValue(
7989
this.fieldBool,
8090
this.fieldString,
@@ -92,6 +102,9 @@ class DefaultValue implements dvi.DefaultValue {
92102
this.constClass = const ConstClass('value'),
93103
this.valueFromConverter = const ConstClass('value'),
94104
this.valueFromFunction = const ConstClass('value'),
105+
required this.intDefaultValueFromFunction,
106+
required this.valueFromDefaultValueDefaultConstructor,
107+
required this.valueFromDefaultValueNamedConstructor,
95108
});
96109

97110
factory DefaultValue.fromJson(Map<String, dynamic> json) =>

‎json_serializable/test/default_value/default_value.g_any_map__checked.g.dart

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎json_serializable/test/default_value/default_value_interface.dart

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ abstract class DefaultValue {
3636
ConstClass get valueFromConverter;
3737

3838
ConstClass get valueFromFunction;
39+
40+
int get intDefaultValueFromFunction;
41+
42+
ConstClass get valueFromDefaultValueDefaultConstructor;
43+
44+
ConstClass get valueFromDefaultValueNamedConstructor;
3945
}
4046

4147
enum Greek { alpha, beta, gamma, delta }
@@ -44,7 +50,9 @@ enum Greek { alpha, beta, gamma, delta }
4450
class ConstClass {
4551
final String field;
4652

47-
const ConstClass(this.field);
53+
const ConstClass([this.field = 'default']);
54+
55+
ConstClass.easy() : field = 'easy';
4856

4957
factory ConstClass.fromJson(Map<String, dynamic> json) => ConstClass(
5058
json['field'] as String,
@@ -68,3 +76,5 @@ class ConstClassConverter extends JsonConverter<ConstClass, String> {
6876
@override
6977
String toJson(ConstClass object) => object.field;
7078
}
79+
80+
int intDefaultValueFunction() => 43;

‎json_serializable/test/default_value/default_value_test.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ const _defaultInstance = {
2929
'constClass': {'field': 'value'},
3030
'valueFromConverter': 'value',
3131
'valueFromFunction': 'value',
32+
'intDefaultValueFromFunction': 43,
33+
'valueFromDefaultValueDefaultConstructor': {'field': 'default'},
34+
'valueFromDefaultValueNamedConstructor': {'field': 'easy'},
3235
};
3336

3437
const _otherValues = {
@@ -50,6 +53,9 @@ const _otherValues = {
5053
'constClass': {'field': 'otherValue'},
5154
'valueFromConverter': 'otherValue',
5255
'valueFromFunction': 'otherValue',
56+
'intDefaultValueFromFunction': 44,
57+
'valueFromDefaultValueDefaultConstructor': {'field': 'other'},
58+
'valueFromDefaultValueNamedConstructor': {'field': 'other'},
5359
};
5460

5561
void main() {

‎json_serializable/test/default_value/implicit_default_value.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ class DefaultValueImplicit implements dvi.DefaultValue {
4646
@JsonKey(fromJson: constClassFromJson, toJson: constClassToJson)
4747
ConstClass valueFromFunction;
4848

49+
int intDefaultValueFromFunction;
50+
51+
ConstClass valueFromDefaultValueDefaultConstructor;
52+
53+
ConstClass valueFromDefaultValueNamedConstructor;
54+
4955
DefaultValueImplicit({
5056
this.fieldBool = true,
5157
this.fieldString = 'string',
@@ -65,6 +71,9 @@ class DefaultValueImplicit implements dvi.DefaultValue {
6571
this.constClass = const ConstClass('value'),
6672
this.valueFromConverter = const ConstClass('value'),
6773
this.valueFromFunction = const ConstClass('value'),
74+
this.intDefaultValueFromFunction = 43,
75+
this.valueFromDefaultValueDefaultConstructor = const ConstClass(),
76+
this.valueFromDefaultValueNamedConstructor = const ConstClass('easy'),
6877
});
6978

7079
factory DefaultValueImplicit.fromJson(Map<String, dynamic> json) =>

‎json_serializable/test/default_value/implicit_default_value.g.dart

Lines changed: 19 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎json_serializable/test/json_serializable_test.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ const _expectedAnnotatedTests = {
5555
'DefaultWithConstObject',
5656
'DefaultWithDisallowNullRequiredClass',
5757
'DefaultWithFunction',
58+
'DefaultWithFunctionInList',
5859
'DefaultWithNestedEnum',
5960
'DefaultWithSymbol',
6061
'DefaultWithToJsonClass',

‎json_serializable/test/src/default_value_input.dart

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,17 @@ class DefaultWithSymbol {
1919

2020
int _function() => 42;
2121

22-
@ShouldThrow(
23-
'Error with `@JsonKey` on the `field` field. '
24-
'`defaultValue` is `Function`, it must be a literal.',
25-
element: 'field',
22+
@ShouldGenerate(
23+
r'''
24+
DefaultWithFunction _$DefaultWithFunctionFromJson(Map<String, dynamic> json) =>
25+
DefaultWithFunction()..field = json['field'] ?? _function();
26+
27+
Map<String, dynamic> _$DefaultWithFunctionToJson(
28+
DefaultWithFunction instance) =>
29+
<String, dynamic>{
30+
'field': instance.field,
31+
};
32+
''',
2633
)
2734
@JsonSerializable()
2835
class DefaultWithFunction {
@@ -32,6 +39,19 @@ class DefaultWithFunction {
3239
DefaultWithFunction();
3340
}
3441

42+
@ShouldThrow(
43+
'Error with `@JsonKey` on the `field` field. '
44+
'`defaultValue` is `List > Function`, it must be a literal.',
45+
element: 'field',
46+
)
47+
@JsonSerializable()
48+
class DefaultWithFunctionInList {
49+
@JsonKey(defaultValue: [_function])
50+
Object? field;
51+
52+
DefaultWithFunctionInList();
53+
}
54+
3555
@ShouldThrow(
3656
'Error with `@JsonKey` on the `field` field. '
3757
'`defaultValue` is `Type`, it must be a literal.',

0 commit comments

Comments
 (0)
Please sign in to comment.