Skip to content

Commit 67b21d2

Browse files
kevmoonatebosch
andauthored
Fix genericArgumentFactories w/ T? in field usage (#840)
When a generic type (`T`) is used in a field as nullable (`final T? someField;`) then we cannot call the defined converters which are not expected to return `null` values (in `fromJson`) or handle `null` argument (in `toJson`). So we do the `null` check in a helper and pass it through. Fixes #803 (for real) Closes #837 Co-authored-by: Nate Bosch <[email protected]>
1 parent 2c6d717 commit 67b21d2

File tree

8 files changed

+382
-55
lines changed

8 files changed

+382
-55
lines changed

json_serializable/CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
## 5.0.0-dev
1+
## 4.1.0-dev
22

33
- Implementation is now null-safe.
4+
- Correctly handle nullable generic fields (`T?`) with
5+
`genericArgumentFactories`.
46

57
## 4.0.3
68

json_serializable/README.md

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -94,25 +94,25 @@ is generated:
9494
| | | [JsonKey.toJson] |
9595
| | | [JsonKey.unknownEnumValue] |
9696

97-
[JsonSerializable.anyMap]: https://pub.dev/documentation/json_annotation/4.0.0/json_annotation/JsonSerializable/anyMap.html
98-
[JsonSerializable.checked]: https://pub.dev/documentation/json_annotation/4.0.0/json_annotation/JsonSerializable/checked.html
99-
[JsonSerializable.createFactory]: https://pub.dev/documentation/json_annotation/4.0.0/json_annotation/JsonSerializable/createFactory.html
100-
[JsonSerializable.createToJson]: https://pub.dev/documentation/json_annotation/4.0.0/json_annotation/JsonSerializable/createToJson.html
101-
[JsonSerializable.disallowUnrecognizedKeys]: https://pub.dev/documentation/json_annotation/4.0.0/json_annotation/JsonSerializable/disallowUnrecognizedKeys.html
102-
[JsonSerializable.explicitToJson]: https://pub.dev/documentation/json_annotation/4.0.0/json_annotation/JsonSerializable/explicitToJson.html
103-
[JsonSerializable.fieldRename]: https://pub.dev/documentation/json_annotation/4.0.0/json_annotation/JsonSerializable/fieldRename.html
104-
[JsonSerializable.genericArgumentFactories]: https://pub.dev/documentation/json_annotation/4.0.0/json_annotation/JsonSerializable/genericArgumentFactories.html
105-
[JsonSerializable.ignoreUnannotated]: https://pub.dev/documentation/json_annotation/4.0.0/json_annotation/JsonSerializable/ignoreUnannotated.html
106-
[JsonSerializable.includeIfNull]: https://pub.dev/documentation/json_annotation/4.0.0/json_annotation/JsonSerializable/includeIfNull.html
107-
[JsonKey.includeIfNull]: https://pub.dev/documentation/json_annotation/4.0.0/json_annotation/JsonKey/includeIfNull.html
108-
[JsonKey.defaultValue]: https://pub.dev/documentation/json_annotation/4.0.0/json_annotation/JsonKey/defaultValue.html
109-
[JsonKey.disallowNullValue]: https://pub.dev/documentation/json_annotation/4.0.0/json_annotation/JsonKey/disallowNullValue.html
110-
[JsonKey.fromJson]: https://pub.dev/documentation/json_annotation/4.0.0/json_annotation/JsonKey/fromJson.html
111-
[JsonKey.ignore]: https://pub.dev/documentation/json_annotation/4.0.0/json_annotation/JsonKey/ignore.html
112-
[JsonKey.name]: https://pub.dev/documentation/json_annotation/4.0.0/json_annotation/JsonKey/name.html
113-
[JsonKey.required]: https://pub.dev/documentation/json_annotation/4.0.0/json_annotation/JsonKey/required.html
114-
[JsonKey.toJson]: https://pub.dev/documentation/json_annotation/4.0.0/json_annotation/JsonKey/toJson.html
115-
[JsonKey.unknownEnumValue]: https://pub.dev/documentation/json_annotation/4.0.0/json_annotation/JsonKey/unknownEnumValue.html
97+
[JsonSerializable.anyMap]: https://pub.dev/documentation/json_annotation/4.0.1/json_annotation/JsonSerializable/anyMap.html
98+
[JsonSerializable.checked]: https://pub.dev/documentation/json_annotation/4.0.1/json_annotation/JsonSerializable/checked.html
99+
[JsonSerializable.createFactory]: https://pub.dev/documentation/json_annotation/4.0.1/json_annotation/JsonSerializable/createFactory.html
100+
[JsonSerializable.createToJson]: https://pub.dev/documentation/json_annotation/4.0.1/json_annotation/JsonSerializable/createToJson.html
101+
[JsonSerializable.disallowUnrecognizedKeys]: https://pub.dev/documentation/json_annotation/4.0.1/json_annotation/JsonSerializable/disallowUnrecognizedKeys.html
102+
[JsonSerializable.explicitToJson]: https://pub.dev/documentation/json_annotation/4.0.1/json_annotation/JsonSerializable/explicitToJson.html
103+
[JsonSerializable.fieldRename]: https://pub.dev/documentation/json_annotation/4.0.1/json_annotation/JsonSerializable/fieldRename.html
104+
[JsonSerializable.genericArgumentFactories]: https://pub.dev/documentation/json_annotation/4.0.1/json_annotation/JsonSerializable/genericArgumentFactories.html
105+
[JsonSerializable.ignoreUnannotated]: https://pub.dev/documentation/json_annotation/4.0.1/json_annotation/JsonSerializable/ignoreUnannotated.html
106+
[JsonSerializable.includeIfNull]: https://pub.dev/documentation/json_annotation/4.0.1/json_annotation/JsonSerializable/includeIfNull.html
107+
[JsonKey.includeIfNull]: https://pub.dev/documentation/json_annotation/4.0.1/json_annotation/JsonKey/includeIfNull.html
108+
[JsonKey.defaultValue]: https://pub.dev/documentation/json_annotation/4.0.1/json_annotation/JsonKey/defaultValue.html
109+
[JsonKey.disallowNullValue]: https://pub.dev/documentation/json_annotation/4.0.1/json_annotation/JsonKey/disallowNullValue.html
110+
[JsonKey.fromJson]: https://pub.dev/documentation/json_annotation/4.0.1/json_annotation/JsonKey/fromJson.html
111+
[JsonKey.ignore]: https://pub.dev/documentation/json_annotation/4.0.1/json_annotation/JsonKey/ignore.html
112+
[JsonKey.name]: https://pub.dev/documentation/json_annotation/4.0.1/json_annotation/JsonKey/name.html
113+
[JsonKey.required]: https://pub.dev/documentation/json_annotation/4.0.1/json_annotation/JsonKey/required.html
114+
[JsonKey.toJson]: https://pub.dev/documentation/json_annotation/4.0.1/json_annotation/JsonKey/toJson.html
115+
[JsonKey.unknownEnumValue]: https://pub.dev/documentation/json_annotation/4.0.1/json_annotation/JsonKey/unknownEnumValue.html
116116

117117
> Note: every `JsonSerializable` field is configurable via `build.yaml`
118118
> see the table for the corresponding key.

json_serializable/doc/doc.md

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -19,22 +19,22 @@
1919
| | | [JsonKey.toJson] |
2020
| | | [JsonKey.unknownEnumValue] |
2121

22-
[JsonSerializable.anyMap]: https://pub.dev/documentation/json_annotation/4.0.0/json_annotation/JsonSerializable/anyMap.html
23-
[JsonSerializable.checked]: https://pub.dev/documentation/json_annotation/4.0.0/json_annotation/JsonSerializable/checked.html
24-
[JsonSerializable.createFactory]: https://pub.dev/documentation/json_annotation/4.0.0/json_annotation/JsonSerializable/createFactory.html
25-
[JsonSerializable.createToJson]: https://pub.dev/documentation/json_annotation/4.0.0/json_annotation/JsonSerializable/createToJson.html
26-
[JsonSerializable.disallowUnrecognizedKeys]: https://pub.dev/documentation/json_annotation/4.0.0/json_annotation/JsonSerializable/disallowUnrecognizedKeys.html
27-
[JsonSerializable.explicitToJson]: https://pub.dev/documentation/json_annotation/4.0.0/json_annotation/JsonSerializable/explicitToJson.html
28-
[JsonSerializable.fieldRename]: https://pub.dev/documentation/json_annotation/4.0.0/json_annotation/JsonSerializable/fieldRename.html
29-
[JsonSerializable.genericArgumentFactories]: https://pub.dev/documentation/json_annotation/4.0.0/json_annotation/JsonSerializable/genericArgumentFactories.html
30-
[JsonSerializable.ignoreUnannotated]: https://pub.dev/documentation/json_annotation/4.0.0/json_annotation/JsonSerializable/ignoreUnannotated.html
31-
[JsonSerializable.includeIfNull]: https://pub.dev/documentation/json_annotation/4.0.0/json_annotation/JsonSerializable/includeIfNull.html
32-
[JsonKey.includeIfNull]: https://pub.dev/documentation/json_annotation/4.0.0/json_annotation/JsonKey/includeIfNull.html
33-
[JsonKey.defaultValue]: https://pub.dev/documentation/json_annotation/4.0.0/json_annotation/JsonKey/defaultValue.html
34-
[JsonKey.disallowNullValue]: https://pub.dev/documentation/json_annotation/4.0.0/json_annotation/JsonKey/disallowNullValue.html
35-
[JsonKey.fromJson]: https://pub.dev/documentation/json_annotation/4.0.0/json_annotation/JsonKey/fromJson.html
36-
[JsonKey.ignore]: https://pub.dev/documentation/json_annotation/4.0.0/json_annotation/JsonKey/ignore.html
37-
[JsonKey.name]: https://pub.dev/documentation/json_annotation/4.0.0/json_annotation/JsonKey/name.html
38-
[JsonKey.required]: https://pub.dev/documentation/json_annotation/4.0.0/json_annotation/JsonKey/required.html
39-
[JsonKey.toJson]: https://pub.dev/documentation/json_annotation/4.0.0/json_annotation/JsonKey/toJson.html
40-
[JsonKey.unknownEnumValue]: https://pub.dev/documentation/json_annotation/4.0.0/json_annotation/JsonKey/unknownEnumValue.html
22+
[JsonSerializable.anyMap]: https://pub.dev/documentation/json_annotation/4.0.1/json_annotation/JsonSerializable/anyMap.html
23+
[JsonSerializable.checked]: https://pub.dev/documentation/json_annotation/4.0.1/json_annotation/JsonSerializable/checked.html
24+
[JsonSerializable.createFactory]: https://pub.dev/documentation/json_annotation/4.0.1/json_annotation/JsonSerializable/createFactory.html
25+
[JsonSerializable.createToJson]: https://pub.dev/documentation/json_annotation/4.0.1/json_annotation/JsonSerializable/createToJson.html
26+
[JsonSerializable.disallowUnrecognizedKeys]: https://pub.dev/documentation/json_annotation/4.0.1/json_annotation/JsonSerializable/disallowUnrecognizedKeys.html
27+
[JsonSerializable.explicitToJson]: https://pub.dev/documentation/json_annotation/4.0.1/json_annotation/JsonSerializable/explicitToJson.html
28+
[JsonSerializable.fieldRename]: https://pub.dev/documentation/json_annotation/4.0.1/json_annotation/JsonSerializable/fieldRename.html
29+
[JsonSerializable.genericArgumentFactories]: https://pub.dev/documentation/json_annotation/4.0.1/json_annotation/JsonSerializable/genericArgumentFactories.html
30+
[JsonSerializable.ignoreUnannotated]: https://pub.dev/documentation/json_annotation/4.0.1/json_annotation/JsonSerializable/ignoreUnannotated.html
31+
[JsonSerializable.includeIfNull]: https://pub.dev/documentation/json_annotation/4.0.1/json_annotation/JsonSerializable/includeIfNull.html
32+
[JsonKey.includeIfNull]: https://pub.dev/documentation/json_annotation/4.0.1/json_annotation/JsonKey/includeIfNull.html
33+
[JsonKey.defaultValue]: https://pub.dev/documentation/json_annotation/4.0.1/json_annotation/JsonKey/defaultValue.html
34+
[JsonKey.disallowNullValue]: https://pub.dev/documentation/json_annotation/4.0.1/json_annotation/JsonKey/disallowNullValue.html
35+
[JsonKey.fromJson]: https://pub.dev/documentation/json_annotation/4.0.1/json_annotation/JsonKey/fromJson.html
36+
[JsonKey.ignore]: https://pub.dev/documentation/json_annotation/4.0.1/json_annotation/JsonKey/ignore.html
37+
[JsonKey.name]: https://pub.dev/documentation/json_annotation/4.0.1/json_annotation/JsonKey/name.html
38+
[JsonKey.required]: https://pub.dev/documentation/json_annotation/4.0.1/json_annotation/JsonKey/required.html
39+
[JsonKey.toJson]: https://pub.dev/documentation/json_annotation/4.0.1/json_annotation/JsonKey/toJson.html
40+
[JsonKey.unknownEnumValue]: https://pub.dev/documentation/json_annotation/4.0.1/json_annotation/JsonKey/unknownEnumValue.html

json_serializable/lib/src/type_helpers/generic_factory_helper.dart

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'package:analyzer/dart/element/type.dart';
66

77
import '../lambda_result.dart';
88
import '../type_helper.dart';
9+
import '../utils.dart';
910

1011
class GenericFactoryHelper extends TypeHelper<TypeHelperContextWithConfig> {
1112
const GenericFactoryHelper();
@@ -18,7 +19,13 @@ class GenericFactoryHelper extends TypeHelper<TypeHelperContextWithConfig> {
1819
) {
1920
if (context.config.genericArgumentFactories! &&
2021
targetType is TypeParameterType) {
21-
return LambdaResult(expression, toJsonForType(targetType));
22+
final toJsonFunc = toJsonForType(targetType);
23+
if (targetType.isNullableType) {
24+
context.addMember(_toJsonHelper);
25+
return '$_toJsonHelperName($expression, $toJsonFunc)';
26+
}
27+
28+
return LambdaResult(expression, toJsonFunc);
2229
}
2330

2431
return null;
@@ -33,13 +40,40 @@ class GenericFactoryHelper extends TypeHelper<TypeHelperContextWithConfig> {
3340
) {
3441
if (context.config.genericArgumentFactories! &&
3542
targetType is TypeParameterType) {
36-
return LambdaResult(expression, fromJsonForType(targetType));
43+
final fromJsonFunc = fromJsonForType(targetType);
44+
45+
if (targetType.isNullableType) {
46+
context.addMember(_fromJsonHelper);
47+
return '$_fromJsonHelperName($expression, $fromJsonFunc)';
48+
}
49+
50+
return LambdaResult(expression, fromJsonFunc);
3751
}
3852

3953
return null;
4054
}
4155
}
4256

57+
const _fromJsonHelperName = r'_$nullableGenericFromJson';
58+
59+
const _fromJsonHelper = '''
60+
T? $_fromJsonHelperName<T>(
61+
Object? input,
62+
T Function(Object? json) fromJson,
63+
) =>
64+
input == null ? null : fromJson(input);
65+
''';
66+
67+
const _toJsonHelperName = r'_$nullableGenericToJson';
68+
69+
const _toJsonHelper = '''
70+
Object? $_toJsonHelperName<T>(
71+
T? input,
72+
Object? Function(T value) toJson,
73+
) =>
74+
input == null ? null : toJson(input);
75+
''';
76+
4377
String toJsonForType(TypeParameterType type) =>
4478
toJsonForName(type.getDisplayString(withNullability: false));
4579

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: 5.0.0-dev
2+
version: 4.1.0-dev
33
description: >-
44
Automatically generate code for converting to and from JSON by annotating
55
Dart classes.
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
// @dart=2.12
6+
7+
import 'package:json_annotation/json_annotation.dart';
8+
9+
part 'generic_argument_factories_nullable.g.dart';
10+
11+
@JsonSerializable(genericArgumentFactories: true)
12+
class GenericClassWithHelpersNullable<T, S> {
13+
final T? value;
14+
15+
final List<T?>? list;
16+
17+
final Set<S?>? someSet;
18+
19+
GenericClassWithHelpersNullable({
20+
this.value,
21+
this.list,
22+
this.someSet,
23+
});
24+
25+
factory GenericClassWithHelpersNullable.fromJson(
26+
Map<String, dynamic> json,
27+
T Function(Object? json) fromJsonT,
28+
S Function(Object? json) fromJsonS,
29+
) =>
30+
_$GenericClassWithHelpersNullableFromJson(json, fromJsonT, fromJsonS);
31+
32+
Map<String, dynamic> toJson(
33+
Object? Function(T value) toJsonT,
34+
Object? Function(S value) toJsonS,
35+
) =>
36+
_$GenericClassWithHelpersNullableToJson(this, toJsonT, toJsonS);
37+
}
38+
39+
@JsonSerializable()
40+
class ConcreteClassNullable {
41+
final GenericClassWithHelpersNullable<int, String> value;
42+
43+
final GenericClassWithHelpersNullable<double, BigInt> value2;
44+
45+
// Regression scenario for google/json_serializable.dart#803
46+
final GenericClassWithHelpersNullable<double?, BigInt?> value3;
47+
48+
ConcreteClassNullable(this.value, this.value2, this.value3);
49+
50+
factory ConcreteClassNullable.fromJson(Map<String, dynamic> json) =>
51+
_$ConcreteClassNullableFromJson(json);
52+
53+
Map<String, dynamic> toJson() => _$ConcreteClassNullableToJson(this);
54+
}

json_serializable/test/generic_files/generic_argument_factories_nullable.g.dart

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

0 commit comments

Comments
 (0)