Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support using non-nullable JsonConverter on nullable properties #1136

Merged
merged 7 commits into from Apr 28, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions json_serializable/lib/src/type_helper.dart
Expand Up @@ -35,6 +35,12 @@ abstract class TypeHelperContextWithConfig extends TypeHelperContext {
ClassConfig get config;
}

class Expression {
Expression(this.expression, this.isNullable);
final String expression;
final bool isNullable;
}

abstract class TypeHelper<T extends TypeHelperContext> {
const TypeHelper();

Expand Down
56 changes: 52 additions & 4 deletions json_serializable/lib/src/type_helpers/json_converter_helper.dart
Expand Up @@ -32,6 +32,19 @@ class JsonConverterHelper extends TypeHelper {
return null;
}

if (!converter.fieldType.isNullableType && targetType.isNullableType) {
// Hacky way to hoist the expression to avoid casting
// Ideally we should be able to cleanly declare a variable in the
// parent code-block, instead of using (){}()
return '''
() {
final val = $expression;
return val == null
? null
: ${converter.accessString}.toJson(val);
}()''';
}

return LambdaResult(expression, '${converter.accessString}.toJson');
}

Expand All @@ -49,6 +62,19 @@ class JsonConverterHelper extends TypeHelper {

final asContent = asStatement(converter.jsonType);

if (!converter.jsonType.isNullableType && targetType.isNullableType) {
// Hacky way to hoist the expression to avoid casting
// Ideally we should be able to cleanly declare a variable in the
// parent code-block, instead of using (){}()
return '''
() {
kevmoo marked this conversation as resolved.
Show resolved Hide resolved
final val = $expression;
return val == null
? null
: ${converter.accessString}.fromJson(val$asContent);
}()''';
}

return LambdaResult(
expression,
'${converter.accessString}.fromJson',
Expand All @@ -60,21 +86,28 @@ class JsonConverterHelper extends TypeHelper {
class _JsonConvertData {
final String accessString;
final DartType jsonType;
final DartType fieldType;

_JsonConvertData.className(
String className,
String accessor,
this.jsonType,
this.fieldType,
) : accessString = 'const $className${_withAccessor(accessor)}()';

_JsonConvertData.genericClass(
String className,
String genericTypeArg,
String accessor,
this.jsonType,
this.fieldType,
) : accessString = '$className<$genericTypeArg>${_withAccessor(accessor)}()';

_JsonConvertData.propertyAccess(this.accessString, this.jsonType);
_JsonConvertData.propertyAccess(
this.accessString,
this.jsonType,
this.fieldType,
);

static String _withAccessor(String accessor) =>
accessor.isEmpty ? '' : '.$accessor';
Expand Down Expand Up @@ -127,7 +160,11 @@ _JsonConvertData? _typeConverterFrom(
accessString = '${enclosing.name}.$accessString';
}

return _JsonConvertData.propertyAccess(accessString, match.jsonType);
return _JsonConvertData.propertyAccess(
accessString,
match.jsonType,
match.fieldType,
);
}

final reviver = ConstantReader(match.annotation).revive();
Expand All @@ -145,18 +182,21 @@ _JsonConvertData? _typeConverterFrom(
match.genericTypeArg!,
reviver.accessor,
match.jsonType,
match.fieldType,
);
}

return _JsonConvertData.className(
match.annotation.type!.element!.name!,
reviver.accessor,
match.jsonType,
match.fieldType,
);
}

class _ConverterMatch {
final DartObject annotation;
final DartType fieldType;
final DartType jsonType;
final ElementAnnotation elementAnnotation;
final String? genericTypeArg;
Expand All @@ -166,6 +206,7 @@ class _ConverterMatch {
this.annotation,
this.jsonType,
this.genericTypeArg,
this.fieldType,
);
}

Expand All @@ -191,9 +232,15 @@ _ConverterMatch? _compatibleMatch(

final fieldType = jsonConverterSuper.typeArguments[0];

if (fieldType == targetType) {
// Allow assigning T to T?
if (fieldType == targetType.promoteNonNullable()) {
return _ConverterMatch(
annotation, constantValue, jsonConverterSuper.typeArguments[1], null);
annotation,
constantValue,
jsonConverterSuper.typeArguments[1],
null,
fieldType,
);
}

if (fieldType is TypeParameterType && targetType is TypeParameterType) {
Expand All @@ -212,6 +259,7 @@ _ConverterMatch? _compatibleMatch(
constantValue,
jsonConverterSuper.typeArguments[1],
'${targetType.element.name}${targetType.isNullableType ? '?' : ''}',
fieldType,
);
}

Expand Down
43 changes: 29 additions & 14 deletions json_serializable/test/generic_files/generic_class.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions json_serializable/test/kitchen_sink/kitchen_sink.dart
Expand Up @@ -66,9 +66,13 @@ class _Factory implements k.KitchenSinkFactory<String, dynamic> {
[],
BigInt.zero,
{},
BigInt.zero,
{},
TrivialNumber(0),
{},
DateTime.fromMillisecondsSinceEpoch(0),
TrivialNumber(0),
{},
);

k.JsonConverterTestClass jsonConverterFromJson(Map<String, dynamic> json) =>
Expand Down Expand Up @@ -203,9 +207,13 @@ class JsonConverterTestClass implements k.JsonConverterTestClass {
this.durationList,
this.bigInt,
this.bigIntMap,
this.nullableBigInt,
this.nullableBigIntMap,
this.numberSilly,
this.numberSillySet,
this.dateTime,
this.nullableNumberSilly,
this.nullableNumberSillySet,
);

factory JsonConverterTestClass.fromJson(Map<String, dynamic> json) =>
Expand All @@ -219,10 +227,16 @@ class JsonConverterTestClass implements k.JsonConverterTestClass {
BigInt bigInt;
Map<String, BigInt> bigIntMap;

BigInt? nullableBigInt;
Map<String, BigInt?> nullableBigIntMap;

TrivialNumber numberSilly;
Set<TrivialNumber> numberSillySet;

DateTime? dateTime;

TrivialNumber? nullableNumberSilly;
Set<TrivialNumber?> nullableNumberSillySet;
}

@JsonSerializable()
Expand Down
58 changes: 52 additions & 6 deletions json_serializable/test/kitchen_sink/kitchen_sink.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions json_serializable/test/kitchen_sink/kitchen_sink.g_any_map.dart
Expand Up @@ -65,9 +65,13 @@ class _Factory implements k.KitchenSinkFactory<dynamic, dynamic> {
[],
BigInt.zero,
{},
BigInt.zero,
{},
TrivialNumber(0),
{},
DateTime.fromMillisecondsSinceEpoch(0),
TrivialNumber(0),
{},
);

k.JsonConverterTestClass jsonConverterFromJson(Map<String, dynamic> json) =>
Expand Down Expand Up @@ -205,9 +209,13 @@ class JsonConverterTestClass implements k.JsonConverterTestClass {
this.durationList,
this.bigInt,
this.bigIntMap,
this.nullableBigInt,
this.nullableBigIntMap,
this.numberSilly,
this.numberSillySet,
this.dateTime,
this.nullableNumberSilly,
this.nullableNumberSillySet,
);

factory JsonConverterTestClass.fromJson(Map<String, dynamic> json) =>
Expand All @@ -221,10 +229,16 @@ class JsonConverterTestClass implements k.JsonConverterTestClass {
BigInt bigInt;
Map<String, BigInt> bigIntMap;

BigInt? nullableBigInt;
Map<String, BigInt?> nullableBigIntMap;

TrivialNumber numberSilly;
Set<TrivialNumber> numberSillySet;

DateTime? dateTime;

TrivialNumber? nullableNumberSilly;
Set<TrivialNumber?> nullableNumberSillySet;
}

@JsonSerializable(
Expand Down