Skip to content

Commit

Permalink
Correctly handle Object?/dynamic as to/from json param type (#1307)
Browse files Browse the repository at this point in the history
  • Loading branch information
kevmoo authored Apr 26, 2023
1 parent 190f8b1 commit 5422fd4
Show file tree
Hide file tree
Showing 17 changed files with 2,162 additions and 45 deletions.
1 change: 1 addition & 0 deletions json_serializable/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## 6.6.2-dev

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

## 6.6.1
Expand Down
4 changes: 2 additions & 2 deletions json_serializable/lib/src/default_container.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import 'utils.dart';
/// a default value available to replace it if `null`.
class DefaultContainer {
final String expression;
final String output;
final Object output;

DefaultContainer(this.expression, this.output);

Expand All @@ -23,7 +23,7 @@ class DefaultContainer {
return ifNullOrElse(
value.expression,
defaultValue ?? 'null',
value.output,
value.output.toString(),
);
}
value = value.output;
Expand Down
38 changes: 36 additions & 2 deletions json_serializable/lib/src/lambda_result.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'package:analyzer/dart/element/type.dart';
import 'package:source_helper/source_helper.dart';

import 'constants.dart' show closureArg;
import 'shared_checkers.dart';
import 'utils.dart';

/// Represents a lambda that can be used as a tear-off depending on the context
/// in which it is used.
Expand All @@ -13,9 +18,11 @@ import 'constants.dart' show closureArg;
class LambdaResult {
final String expression;
final String lambda;
final String? asContent;
final DartType? asContent;

String get _asContent => asContent == null ? '' : _asStatement(asContent!);

String get _fullExpression => '$expression${asContent ?? ''}';
String get _fullExpression => '$expression$_asContent';

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

Expand All @@ -27,3 +34,30 @@ class LambdaResult {
? subField.lambda
: '($closureArg) => $subField';
}

String _asStatement(DartType type) {
if (type.isLikeDynamic) {
return '';
}

final nullableSuffix = type.isNullableType ? '?' : '';

if (coreIterableTypeChecker.isAssignableFromType(type)) {
final itemType = coreIterableGenericType(type);
if (itemType.isLikeDynamic) {
return ' as List$nullableSuffix';
}
}

if (coreMapTypeChecker.isAssignableFromType(type)) {
final args = type.typeArgumentsOf(coreMapTypeChecker)!;
assert(args.length == 2);

if (args.every((e) => e.isLikeDynamic)) {
return ' as Map$nullableSuffix';
}
}

final typeCode = typeToCode(type);
return ' as $typeCode';
}
29 changes: 0 additions & 29 deletions json_serializable/lib/src/shared_checkers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import 'package:analyzer/dart/element/type.dart';
import 'package:source_gen/source_gen.dart' show TypeChecker;
import 'package:source_helper/source_helper.dart';

import 'utils.dart';

/// A [TypeChecker] for [Iterable].
const coreIterableTypeChecker = TypeChecker.fromUrl('dart:core#Iterable');

Expand All @@ -27,30 +25,3 @@ const simpleJsonTypeChecker = TypeChecker.any([
TypeChecker.fromUrl('dart:core#bool'),
TypeChecker.fromUrl('dart:core#num')
]);

String asStatement(DartType type) {
if (type.isLikeDynamic) {
return '';
}

final nullableSuffix = type.isNullableType ? '?' : '';

if (coreIterableTypeChecker.isAssignableFromType(type)) {
final itemType = coreIterableGenericType(type);
if (itemType.isLikeDynamic) {
return ' as List$nullableSuffix';
}
}

if (coreMapTypeChecker.isAssignableFromType(type)) {
final args = type.typeArgumentsOf(coreMapTypeChecker)!;
assert(args.length == 2);

if (args.every((e) => e.isLikeDynamic)) {
return ' as Map$nullableSuffix';
}
}

final typeCode = typeToCode(type);
return ' as $typeCode';
}
8 changes: 5 additions & 3 deletions json_serializable/lib/src/type_helpers/convert_helper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import 'package:analyzer/dart/element/type.dart';
import 'package:source_helper/source_helper.dart';

import '../lambda_result.dart';
import '../shared_checkers.dart';
import '../type_helper.dart';

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

final asContent = asStatement(fromJsonData.paramType);
return LambdaResult(expression, fromJsonData.name, asContent: asContent);
return LambdaResult(
expression,
fromJsonData.name,
asContent: fromJsonData.paramType,
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import 'package:source_gen/source_gen.dart';
import 'package:source_helper/source_helper.dart';

import '../lambda_result.dart';
import '../shared_checkers.dart';
import '../type_helper.dart';
import '../utils.dart';

Expand Down Expand Up @@ -65,8 +64,6 @@ Json? $converterToJsonName<Json, Value>(
return null;
}

final asContent = asStatement(converter.jsonType);

if (!converter.jsonType.isNullableType && targetType.isNullableType) {
const converterFromJsonName = r'_$JsonConverterFromJson';
context.addMember('''
Expand All @@ -88,7 +85,7 @@ Value? $converterFromJsonName<Json, Value>(
return LambdaResult(
expression,
'${converter.accessString}.fromJson',
asContent: asContent,
asContent: converter.jsonType,
);
}
}
Expand Down
12 changes: 7 additions & 5 deletions json_serializable/lib/src/type_helpers/json_helper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
// BSD-style license that can be found in the LICENSE file.

import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/nullability_suffix.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:collection/collection.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:source_gen/source_gen.dart';
import 'package:source_helper/source_helper.dart';

import '../default_container.dart';
import '../lambda_result.dart';
import '../type_helper.dart';
import '../utils.dart';
import 'config_types.dart';
Expand Down Expand Up @@ -130,9 +130,12 @@ class JsonHelper extends TypeHelper<TypeHelperContextWithConfig> {

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

return DefaultContainer(expression, output);
return DefaultContainer(expression, lambda);
}
}

Expand Down Expand Up @@ -263,8 +266,7 @@ InterfaceType? _instantiate(

return ctorParamType.element.instantiate(
typeArguments: argTypes.cast<DartType>(),
// TODO: not 100% sure nullabilitySuffix is right... Works for now
nullabilitySuffix: NullabilitySuffix.none,
nullabilitySuffix: ctorParamType.nullabilitySuffix,
);
}

Expand Down
33 changes: 33 additions & 0 deletions json_serializable/test/supported_types/enum_type.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,36 @@
// BSD-style license that can be found in the LICENSE file.

enum EnumType { alpha, beta, gamma, delta }

class FromJsonDynamicParam {
FromJsonDynamicParam({required this.value});

final int value;

factory FromJsonDynamicParam.fromJson(dynamic json) =>
FromJsonDynamicParam(value: json as int);

dynamic toJson() => null;
}

class FromJsonNullableObjectParam {
FromJsonNullableObjectParam({required this.value});

final int value;

factory FromJsonNullableObjectParam.fromJson(Object? json) =>
FromJsonNullableObjectParam(value: json as int);

Object? toJson() => null;
}

class FromJsonObjectParam {
FromJsonObjectParam({required this.value});

final int value;

factory FromJsonObjectParam.fromJson(Object json) =>
FromJsonObjectParam(value: json as int);

dynamic toJson() => null;
}
96 changes: 96 additions & 0 deletions json_serializable/test/supported_types/input.type_iterable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,102 @@ class SimpleClassNullableOfEnumTypeNullable {
_$SimpleClassNullableOfEnumTypeNullableToJson(this);
}

@JsonSerializable()
class SimpleClassOfFromJsonDynamicParam {
final Iterable<FromJsonDynamicParam> value;

SimpleClassOfFromJsonDynamicParam(
this.value,
);

factory SimpleClassOfFromJsonDynamicParam.fromJson(
Map<String, Object?> json) =>
_$SimpleClassOfFromJsonDynamicParamFromJson(json);

Map<String, Object?> toJson() =>
_$SimpleClassOfFromJsonDynamicParamToJson(this);
}

@JsonSerializable()
class SimpleClassNullableOfFromJsonDynamicParam {
final Iterable<FromJsonDynamicParam>? value;

SimpleClassNullableOfFromJsonDynamicParam(
this.value,
);

factory SimpleClassNullableOfFromJsonDynamicParam.fromJson(
Map<String, Object?> json) =>
_$SimpleClassNullableOfFromJsonDynamicParamFromJson(json);

Map<String, Object?> toJson() =>
_$SimpleClassNullableOfFromJsonDynamicParamToJson(this);
}

@JsonSerializable()
class SimpleClassOfFromJsonNullableObjectParam {
final Iterable<FromJsonNullableObjectParam> value;

SimpleClassOfFromJsonNullableObjectParam(
this.value,
);

factory SimpleClassOfFromJsonNullableObjectParam.fromJson(
Map<String, Object?> json) =>
_$SimpleClassOfFromJsonNullableObjectParamFromJson(json);

Map<String, Object?> toJson() =>
_$SimpleClassOfFromJsonNullableObjectParamToJson(this);
}

@JsonSerializable()
class SimpleClassNullableOfFromJsonNullableObjectParam {
final Iterable<FromJsonNullableObjectParam>? value;

SimpleClassNullableOfFromJsonNullableObjectParam(
this.value,
);

factory SimpleClassNullableOfFromJsonNullableObjectParam.fromJson(
Map<String, Object?> json) =>
_$SimpleClassNullableOfFromJsonNullableObjectParamFromJson(json);

Map<String, Object?> toJson() =>
_$SimpleClassNullableOfFromJsonNullableObjectParamToJson(this);
}

@JsonSerializable()
class SimpleClassOfFromJsonObjectParam {
final Iterable<FromJsonObjectParam> value;

SimpleClassOfFromJsonObjectParam(
this.value,
);

factory SimpleClassOfFromJsonObjectParam.fromJson(
Map<String, Object?> json) =>
_$SimpleClassOfFromJsonObjectParamFromJson(json);

Map<String, Object?> toJson() =>
_$SimpleClassOfFromJsonObjectParamToJson(this);
}

@JsonSerializable()
class SimpleClassNullableOfFromJsonObjectParam {
final Iterable<FromJsonObjectParam>? value;

SimpleClassNullableOfFromJsonObjectParam(
this.value,
);

factory SimpleClassNullableOfFromJsonObjectParam.fromJson(
Map<String, Object?> json) =>
_$SimpleClassNullableOfFromJsonObjectParamFromJson(json);

Map<String, Object?> toJson() =>
_$SimpleClassNullableOfFromJsonObjectParamToJson(this);
}

@JsonSerializable()
class SimpleClassOfInt {
final Iterable<int> value;
Expand Down
Loading

0 comments on commit 5422fd4

Please sign in to comment.