Skip to content

Commit 5422fd4

Browse files
authored
Correctly handle Object?/dynamic as to/from json param type (#1307)
1 parent 190f8b1 commit 5422fd4

17 files changed

+2162
-45
lines changed

json_serializable/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
## 6.6.2-dev
22

3+
- Better handling of `Object?` or `dynamic` as `fromJson` constructor param.
34
- Require Dart 2.19
45

56
## 6.6.1

json_serializable/lib/src/default_container.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import 'utils.dart';
99
/// a default value available to replace it if `null`.
1010
class DefaultContainer {
1111
final String expression;
12-
final String output;
12+
final Object output;
1313

1414
DefaultContainer(this.expression, this.output);
1515

@@ -23,7 +23,7 @@ class DefaultContainer {
2323
return ifNullOrElse(
2424
value.expression,
2525
defaultValue ?? 'null',
26-
value.output,
26+
value.output.toString(),
2727
);
2828
}
2929
value = value.output;

json_serializable/lib/src/lambda_result.dart

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5+
import 'package:analyzer/dart/element/type.dart';
6+
import 'package:source_helper/source_helper.dart';
7+
58
import 'constants.dart' show closureArg;
9+
import 'shared_checkers.dart';
10+
import 'utils.dart';
611

712
/// Represents a lambda that can be used as a tear-off depending on the context
813
/// in which it is used.
@@ -13,9 +18,11 @@ import 'constants.dart' show closureArg;
1318
class LambdaResult {
1419
final String expression;
1520
final String lambda;
16-
final String? asContent;
21+
final DartType? asContent;
22+
23+
String get _asContent => asContent == null ? '' : _asStatement(asContent!);
1724

18-
String get _fullExpression => '$expression${asContent ?? ''}';
25+
String get _fullExpression => '$expression$_asContent';
1926

2027
LambdaResult(this.expression, this.lambda, {this.asContent});
2128

@@ -27,3 +34,30 @@ class LambdaResult {
2734
? subField.lambda
2835
: '($closureArg) => $subField';
2936
}
37+
38+
String _asStatement(DartType type) {
39+
if (type.isLikeDynamic) {
40+
return '';
41+
}
42+
43+
final nullableSuffix = type.isNullableType ? '?' : '';
44+
45+
if (coreIterableTypeChecker.isAssignableFromType(type)) {
46+
final itemType = coreIterableGenericType(type);
47+
if (itemType.isLikeDynamic) {
48+
return ' as List$nullableSuffix';
49+
}
50+
}
51+
52+
if (coreMapTypeChecker.isAssignableFromType(type)) {
53+
final args = type.typeArgumentsOf(coreMapTypeChecker)!;
54+
assert(args.length == 2);
55+
56+
if (args.every((e) => e.isLikeDynamic)) {
57+
return ' as Map$nullableSuffix';
58+
}
59+
}
60+
61+
final typeCode = typeToCode(type);
62+
return ' as $typeCode';
63+
}

json_serializable/lib/src/shared_checkers.dart

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ import 'package:analyzer/dart/element/type.dart';
66
import 'package:source_gen/source_gen.dart' show TypeChecker;
77
import 'package:source_helper/source_helper.dart';
88

9-
import 'utils.dart';
10-
119
/// A [TypeChecker] for [Iterable].
1210
const coreIterableTypeChecker = TypeChecker.fromUrl('dart:core#Iterable');
1311

@@ -27,30 +25,3 @@ const simpleJsonTypeChecker = TypeChecker.any([
2725
TypeChecker.fromUrl('dart:core#bool'),
2826
TypeChecker.fromUrl('dart:core#num')
2927
]);
30-
31-
String asStatement(DartType type) {
32-
if (type.isLikeDynamic) {
33-
return '';
34-
}
35-
36-
final nullableSuffix = type.isNullableType ? '?' : '';
37-
38-
if (coreIterableTypeChecker.isAssignableFromType(type)) {
39-
final itemType = coreIterableGenericType(type);
40-
if (itemType.isLikeDynamic) {
41-
return ' as List$nullableSuffix';
42-
}
43-
}
44-
45-
if (coreMapTypeChecker.isAssignableFromType(type)) {
46-
final args = type.typeArgumentsOf(coreMapTypeChecker)!;
47-
assert(args.length == 2);
48-
49-
if (args.every((e) => e.isLikeDynamic)) {
50-
return ' as Map$nullableSuffix';
51-
}
52-
}
53-
54-
final typeCode = typeToCode(type);
55-
return ' as $typeCode';
56-
}

json_serializable/lib/src/type_helpers/convert_helper.dart

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

88
import '../lambda_result.dart';
9-
import '../shared_checkers.dart';
109
import '../type_helper.dart';
1110

1211
/// Information used by [ConvertHelper] when handling `JsonKey`-annotated
@@ -57,7 +56,10 @@ class ConvertHelper extends TypeHelper<TypeHelperContextWithConvert> {
5756
return null;
5857
}
5958

60-
final asContent = asStatement(fromJsonData.paramType);
61-
return LambdaResult(expression, fromJsonData.name, asContent: asContent);
59+
return LambdaResult(
60+
expression,
61+
fromJsonData.name,
62+
asContent: fromJsonData.paramType,
63+
);
6264
}
6365
}

json_serializable/lib/src/type_helpers/json_converter_helper.dart

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import 'package:source_gen/source_gen.dart';
1111
import 'package:source_helper/source_helper.dart';
1212

1313
import '../lambda_result.dart';
14-
import '../shared_checkers.dart';
1514
import '../type_helper.dart';
1615
import '../utils.dart';
1716

@@ -65,8 +64,6 @@ Json? $converterToJsonName<Json, Value>(
6564
return null;
6665
}
6766

68-
final asContent = asStatement(converter.jsonType);
69-
7067
if (!converter.jsonType.isNullableType && targetType.isNullableType) {
7168
const converterFromJsonName = r'_$JsonConverterFromJson';
7269
context.addMember('''
@@ -88,7 +85,7 @@ Value? $converterFromJsonName<Json, Value>(
8885
return LambdaResult(
8986
expression,
9087
'${converter.accessString}.fromJson',
91-
asContent: asContent,
88+
asContent: converter.jsonType,
9289
);
9390
}
9491
}

json_serializable/lib/src/type_helpers/json_helper.dart

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@
33
// BSD-style license that can be found in the LICENSE file.
44

55
import 'package:analyzer/dart/element/element.dart';
6-
import 'package:analyzer/dart/element/nullability_suffix.dart';
76
import 'package:analyzer/dart/element/type.dart';
87
import 'package:collection/collection.dart';
98
import 'package:json_annotation/json_annotation.dart';
109
import 'package:source_gen/source_gen.dart';
1110
import 'package:source_helper/source_helper.dart';
1211

1312
import '../default_container.dart';
13+
import '../lambda_result.dart';
1414
import '../type_helper.dart';
1515
import '../utils.dart';
1616
import 'config_types.dart';
@@ -130,9 +130,12 @@ class JsonHelper extends TypeHelper<TypeHelperContextWithConfig> {
130130

131131
// TODO: the type could be imported from a library with a prefix!
132132
// https://github.com/google/json_serializable.dart/issues/19
133-
output = '${typeToCode(targetType.promoteNonNullable())}.fromJson($output)';
133+
final lambda = LambdaResult(
134+
output,
135+
'${typeToCode(targetType.promoteNonNullable())}.fromJson',
136+
);
134137

135-
return DefaultContainer(expression, output);
138+
return DefaultContainer(expression, lambda);
136139
}
137140
}
138141

@@ -263,8 +266,7 @@ InterfaceType? _instantiate(
263266

264267
return ctorParamType.element.instantiate(
265268
typeArguments: argTypes.cast<DartType>(),
266-
// TODO: not 100% sure nullabilitySuffix is right... Works for now
267-
nullabilitySuffix: NullabilitySuffix.none,
269+
nullabilitySuffix: ctorParamType.nullabilitySuffix,
268270
);
269271
}
270272

json_serializable/test/supported_types/enum_type.dart

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,36 @@
33
// BSD-style license that can be found in the LICENSE file.
44

55
enum EnumType { alpha, beta, gamma, delta }
6+
7+
class FromJsonDynamicParam {
8+
FromJsonDynamicParam({required this.value});
9+
10+
final int value;
11+
12+
factory FromJsonDynamicParam.fromJson(dynamic json) =>
13+
FromJsonDynamicParam(value: json as int);
14+
15+
dynamic toJson() => null;
16+
}
17+
18+
class FromJsonNullableObjectParam {
19+
FromJsonNullableObjectParam({required this.value});
20+
21+
final int value;
22+
23+
factory FromJsonNullableObjectParam.fromJson(Object? json) =>
24+
FromJsonNullableObjectParam(value: json as int);
25+
26+
Object? toJson() => null;
27+
}
28+
29+
class FromJsonObjectParam {
30+
FromJsonObjectParam({required this.value});
31+
32+
final int value;
33+
34+
factory FromJsonObjectParam.fromJson(Object json) =>
35+
FromJsonObjectParam(value: json as int);
36+
37+
dynamic toJson() => null;
38+
}

json_serializable/test/supported_types/input.type_iterable.dart

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,102 @@ class SimpleClassNullableOfEnumTypeNullable {
419419
_$SimpleClassNullableOfEnumTypeNullableToJson(this);
420420
}
421421

422+
@JsonSerializable()
423+
class SimpleClassOfFromJsonDynamicParam {
424+
final Iterable<FromJsonDynamicParam> value;
425+
426+
SimpleClassOfFromJsonDynamicParam(
427+
this.value,
428+
);
429+
430+
factory SimpleClassOfFromJsonDynamicParam.fromJson(
431+
Map<String, Object?> json) =>
432+
_$SimpleClassOfFromJsonDynamicParamFromJson(json);
433+
434+
Map<String, Object?> toJson() =>
435+
_$SimpleClassOfFromJsonDynamicParamToJson(this);
436+
}
437+
438+
@JsonSerializable()
439+
class SimpleClassNullableOfFromJsonDynamicParam {
440+
final Iterable<FromJsonDynamicParam>? value;
441+
442+
SimpleClassNullableOfFromJsonDynamicParam(
443+
this.value,
444+
);
445+
446+
factory SimpleClassNullableOfFromJsonDynamicParam.fromJson(
447+
Map<String, Object?> json) =>
448+
_$SimpleClassNullableOfFromJsonDynamicParamFromJson(json);
449+
450+
Map<String, Object?> toJson() =>
451+
_$SimpleClassNullableOfFromJsonDynamicParamToJson(this);
452+
}
453+
454+
@JsonSerializable()
455+
class SimpleClassOfFromJsonNullableObjectParam {
456+
final Iterable<FromJsonNullableObjectParam> value;
457+
458+
SimpleClassOfFromJsonNullableObjectParam(
459+
this.value,
460+
);
461+
462+
factory SimpleClassOfFromJsonNullableObjectParam.fromJson(
463+
Map<String, Object?> json) =>
464+
_$SimpleClassOfFromJsonNullableObjectParamFromJson(json);
465+
466+
Map<String, Object?> toJson() =>
467+
_$SimpleClassOfFromJsonNullableObjectParamToJson(this);
468+
}
469+
470+
@JsonSerializable()
471+
class SimpleClassNullableOfFromJsonNullableObjectParam {
472+
final Iterable<FromJsonNullableObjectParam>? value;
473+
474+
SimpleClassNullableOfFromJsonNullableObjectParam(
475+
this.value,
476+
);
477+
478+
factory SimpleClassNullableOfFromJsonNullableObjectParam.fromJson(
479+
Map<String, Object?> json) =>
480+
_$SimpleClassNullableOfFromJsonNullableObjectParamFromJson(json);
481+
482+
Map<String, Object?> toJson() =>
483+
_$SimpleClassNullableOfFromJsonNullableObjectParamToJson(this);
484+
}
485+
486+
@JsonSerializable()
487+
class SimpleClassOfFromJsonObjectParam {
488+
final Iterable<FromJsonObjectParam> value;
489+
490+
SimpleClassOfFromJsonObjectParam(
491+
this.value,
492+
);
493+
494+
factory SimpleClassOfFromJsonObjectParam.fromJson(
495+
Map<String, Object?> json) =>
496+
_$SimpleClassOfFromJsonObjectParamFromJson(json);
497+
498+
Map<String, Object?> toJson() =>
499+
_$SimpleClassOfFromJsonObjectParamToJson(this);
500+
}
501+
502+
@JsonSerializable()
503+
class SimpleClassNullableOfFromJsonObjectParam {
504+
final Iterable<FromJsonObjectParam>? value;
505+
506+
SimpleClassNullableOfFromJsonObjectParam(
507+
this.value,
508+
);
509+
510+
factory SimpleClassNullableOfFromJsonObjectParam.fromJson(
511+
Map<String, Object?> json) =>
512+
_$SimpleClassNullableOfFromJsonObjectParamFromJson(json);
513+
514+
Map<String, Object?> toJson() =>
515+
_$SimpleClassNullableOfFromJsonObjectParamToJson(this);
516+
}
517+
422518
@JsonSerializable()
423519
class SimpleClassOfInt {
424520
final Iterable<int> value;

0 commit comments

Comments
 (0)