Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions json_serializable/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## 2.2.0

- If a field has a conversion function defined – either `JsonKey.toJson` or a
custom `JsonConverter` annotation – handle the case where the function
returns `null` and both `nullable` and `includeIfNull` are `false`.

## 2.1.2

* Support `package:json_annotation` `>=2.1.0 <2.3.0`.
Expand Down
95 changes: 87 additions & 8 deletions json_serializable/lib/src/encoder_helper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import 'package:json_annotation/json_annotation.dart';
import 'constants.dart';
import 'helper_core.dart';
import 'type_helper.dart';
import 'type_helpers/convert_helper.dart';
import 'type_helpers/json_converter_helper.dart';

abstract class EncodeHelper implements HelperCore {
String _fieldAccess(FieldElement field) {
Expand Down Expand Up @@ -110,7 +112,8 @@ class ${_wrapperClassName(true)} extends \$JsonMapWrapper {
String check;

if (!_writeJsonValueNaive(field)) {
check = '_v.${field.name} != null';
final expression = _wrapCustomEncoder('_v.${field.name}', field);
check = '$expression != null';

if (!jsonKeyFor(field).encodeEmptyCollection) {
assert(!jsonKeyFor(field).includeIfNull);
Expand Down Expand Up @@ -248,12 +251,88 @@ class ${_wrapperClassName(true)} extends \$JsonMapWrapper {

/// Returns `true` if the field can be written to JSON 'naively' – meaning
/// we can avoid checking for `null`.
bool _writeJsonValueNaive(FieldElement field) {
final jsonKey = jsonKeyFor(field);

if (jsonKey.includeIfNull) {
return true;
}

if (!jsonKey.nullable &&
jsonKey.encodeEmptyCollection &&
!_fieldHasCustomEncoder(field)) {
return true;
}

return false;
}

/// Returns `true` if [field] has a user-defined encoder.
///
/// `true` if either:
/// `includeIfNull` is `true`
/// or
/// `nullable` is `false` and `encodeEmptyCollection` is true
bool _writeJsonValueNaive(FieldElement field) =>
jsonKeyFor(field).includeIfNull ||
(!jsonKeyFor(field).nullable && jsonKeyFor(field).encodeEmptyCollection);
/// This can be either a `toJson` function in [JsonKey] or a [JsonConverter]
/// annotation.
bool _fieldHasCustomEncoder(FieldElement field) {
final helperContext = getHelperContext(field);

if (helperContext.serializeConvertData != null) {
return true;
}

final output = const JsonConverterHelper()
.serialize(field.type, 'test', helperContext);

if (output != null) {
return true;
}
return false;
}

/// If [field] has a user-defined encoder, return [expression] wrapped in
/// the corresponding conversion logic so we can do a correct `null` check.
///
/// This can be either a `toJson` function in [JsonKey] or a [JsonConverter]
/// annotation.
///
/// If there is no user-defined encoder, just return [expression] as-is.
String _wrapCustomEncoder(String expression, FieldElement field) {
final helperContext = getHelperContext(field);

final convertData = helperContext.serializeConvertData;

var result = expression;
if (convertData != null) {
result = toJsonSerializeImpl(
getHelperContext(field).serializeConvertData.name,
expression,
jsonKeyFor(field).nullable,
);
} else {
final output = const JsonConverterHelper()
.serialize(field.type, expression, helperContext);

if (output != null) {
result = output.toString();
}
}

assert(
(result != expression) == _fieldHasCustomEncoder(field),
'If the output expression is different, then it should map to a field '
'with a custom encoder',
);

if (result == expression) {
// No conversion
return expression;
}

if (jsonKeyFor(field).nullable) {
// If there was a conversion and the field is nullable, wrap the output
// in () – there will be null checks that will break the comparison
// in the caller
result = '($result)';
}

return result;
}
}
10 changes: 8 additions & 2 deletions json_serializable/lib/src/type_helpers/convert_helper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,7 @@ class ConvertHelper extends TypeHelper<TypeHelperContextWithConvert> {
if (toJsonData != null) {
assert(toJsonData.paramType is TypeParameterType ||
targetType.isAssignableTo(toJsonData.paramType));
final result = '${toJsonData.name}($expression)';
return commonNullPrefix(context.nullable, expression, result).toString();
return toJsonSerializeImpl(toJsonData.name, expression, context.nullable);
}
return null;
}
Expand All @@ -49,3 +48,10 @@ class ConvertHelper extends TypeHelper<TypeHelperContextWithConvert> {
return null;
}
}

/// Exposed to support `EncodeHelper` – not exposed publicly.
String toJsonSerializeImpl(
String toJsonDataName, String expression, bool nullable) {
final result = '$toJsonDataName($expression)';
return commonNullPrefix(nullable, expression, result).toString();
}
2 changes: 1 addition & 1 deletion json_serializable/lib/type_helper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export 'src/shared_checkers.dart' show simpleJsonTypeChecker, typeArgumentsOf;
export 'src/type_helper.dart'
show TypeHelperContext, TypeHelper, UnsupportedTypeError;
export 'src/type_helpers/big_int_helper.dart';
export 'src/type_helpers/convert_helper.dart';
export 'src/type_helpers/convert_helper.dart' hide toJsonSerializeImpl;
export 'src/type_helpers/date_time_helper.dart';
export 'src/type_helpers/enum_helper.dart';
export 'src/type_helpers/iterable_helper.dart';
Expand Down
2 changes: 1 addition & 1 deletion json_serializable/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: json_serializable
version: 2.1.2
version: 2.2.0
author: Dart Team <misc@dartlang.org>
description: >-
Automatically generate code for converting to and from JSON by annotating
Expand Down
3 changes: 3 additions & 0 deletions json_serializable/test/json_serializable_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ const _expectedAnnotatedTests = [
'SubTypeWithAnnotatedFieldOverrideExtendsWithOverrides',
'SubTypeWithAnnotatedFieldOverrideImplements',
'theAnswer',
'ToJsonIncludeIfNullFalseWrapped',
'ToJsonNullableFalseIncludeIfNullFalse',
'ToJsonNullableFalseIncludeIfNullFalseWrapped',
'TypedConvertMethods',
'UnknownCtorParamType',
'UnknownFieldType',
Expand Down

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

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

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

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

Loading