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

Allow extra non-serializable fields to be passed into fromJson and ignored in toJson #783

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
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
10 changes: 10 additions & 0 deletions json_annotation/lib/src/json_key.dart
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,15 @@ class JsonKey {
/// valid on a nullable enum field.
final Enum? unknownEnumValue;

/// `true` if this field should not be serialized.
///
/// Fields annotated with [extra] set to true will not be included in JSON
/// output, and their values may be passed in as additional arguments
/// alongside JSON data in the deserialization function.
///
/// If `null` (the default) or false, this argument has no effect.
final bool? extra;

/// Creates a new [JsonKey] instance.
///
/// Only required when the default behavior is not desired.
Expand All @@ -127,6 +136,7 @@ class JsonKey {
this.required,
this.toJson,
this.unknownEnumValue,
this.extra,
});

/// Sentinel value for use with [unknownEnumValue].
Expand Down
28 changes: 22 additions & 6 deletions json_serializable/lib/src/decode_helper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ abstract class DecodeHelper implements HelperCore {
CreateFactoryResult createFactory(
Map<String, FieldElement> accessibleFields,
Map<String, String> unavailableReasons,
List<FieldElement> extras,
) {
assert(config.createFactory);
final buffer = StringBuffer();
Expand All @@ -47,6 +48,16 @@ abstract class DecodeHelper implements HelperCore {
}
}

if (extras.isNotEmpty) {
buffer.writeln(', {');
for (final extra in extras) {
if (!extra.type.isNullableType) buffer.write('required ');
buffer.writeln(
'${extra.type.getDisplayString(withNullability: true)} ${extra.name},');
}
buffer.write('}');
}

buffer.write(')');

final fromJsonLines = <String>[];
Expand All @@ -56,15 +67,17 @@ abstract class DecodeHelper implements HelperCore {
_deserializeForField(accessibleFields[paramOrFieldName]!,
ctorParam: ctorParam);

final extraNames = [for (final extra in extras) extra.name];
final data = _writeConstructorInvocation(
element,
config.constructor,
accessibleFields.keys,
[...accessibleFields.keys, ...extraNames],
accessibleFields.values
.where((fe) => element.lookUpSetter(fe.name, element.library) != null)
.map((fe) => fe.name)
.toList(),
unavailableReasons,
extraNames,
deserializeFun,
);

Expand Down Expand Up @@ -265,6 +278,7 @@ _ConstructorData _writeConstructorInvocation(
Iterable<String> availableConstructorParameters,
Iterable<String> writableFields,
Map<String, String> unavailableReasons,
List<String> extras,
String Function(String paramOrFieldName, {ParameterElement ctorParam})
deserializeForField,
) {
Expand Down Expand Up @@ -301,7 +315,7 @@ _ConstructorData _writeConstructorInvocation(
} else {
constructorArguments.add(arg);
}
usedCtorParamsAndFields.add(arg.name);
if (!extras.contains(arg.name)) usedCtorParamsAndFields.add(arg.name);
}

// fields that aren't already set by the constructor and that aren't final
Expand All @@ -320,17 +334,19 @@ _ConstructorData _writeConstructorInvocation(
buffer
..writeln()
..writeAll(constructorArguments.map((paramElement) {
final content =
deserializeForField(paramElement.name, ctorParam: paramElement);
final content = extras.contains(paramElement.name)
? paramElement.name
: deserializeForField(paramElement.name, ctorParam: paramElement);
return ' $content,\n';
}));
}
if (namedConstructorArguments.isNotEmpty) {
buffer
..writeln()
..writeAll(namedConstructorArguments.map((paramElement) {
final value =
deserializeForField(paramElement.name, ctorParam: paramElement);
final value = extras.contains(paramElement.name)
? paramElement.name
: deserializeForField(paramElement.name, ctorParam: paramElement);
return ' ${paramElement.name}: $value,\n';
}));
}
Expand Down
21 changes: 14 additions & 7 deletions json_serializable/lib/src/generator_helper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ class GeneratorHelper extends HelperCore with EncodeHelper, DecodeHelper {
// these fields.
final unavailableReasons = <String, String>{};

final extras = <FieldElement>[];

final accessibleFields = sortedFields.fold<Map<String, FieldElement>>(
<String, FieldElement>{},
(map, field) {
Expand All @@ -68,21 +70,26 @@ class GeneratorHelper extends HelperCore with EncodeHelper, DecodeHelper {
unavailableReasons[field.name] =
'Setter-only properties are not supported.';
log.warning('Setters are ignored: ${element.name}.${field.name}');
} else if (jsonKeyFor(field).ignore) {
unavailableReasons[field.name] =
'It is assigned to an ignored field.';
} else {
assert(!map.containsKey(field.name));
map[field.name] = field;
final jsonKey = jsonKeyFor(field);
if (jsonKey.ignore) {
unavailableReasons[field.name] =
'It is assigned to an ignored field.';
} else if (jsonKey.extra) {
extras.add(field);
} else {
assert(!map.containsKey(field.name));
map[field.name] = field;
}
}

return map;
},
);

var accessibleFieldSet = accessibleFields.values.toSet();
if (config.createFactory) {
final createResult = createFactory(accessibleFields, unavailableReasons);
final createResult =
createFactory(accessibleFields, unavailableReasons, extras);
yield createResult.output;

accessibleFieldSet = accessibleFields.entries
Expand Down
3 changes: 3 additions & 0 deletions json_serializable/lib/src/json_key_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ KeyConfig _from(FieldElement element, ClassConfig classAnnotation) {
readValueFunctionName: readValueFunctionName,
required: obj.read('required').literalValue as bool?,
unknownEnumValue: _annotationValue('unknownEnumValue', mustBeEnum: true),
extra: obj.read('extra').literalValue as bool?,
);
}

Expand All @@ -241,6 +242,7 @@ KeyConfig _populateJsonKey(
String? readValueFunctionName,
bool? required,
String? unknownEnumValue,
bool? extra,
}) {
if (disallowNullValue == true) {
if (includeIfNull == true) {
Expand All @@ -261,6 +263,7 @@ KeyConfig _populateJsonKey(
readValueFunctionName: readValueFunctionName,
required: required ?? false,
unknownEnumValue: unknownEnumValue,
extra: extra ?? false,
);
}

Expand Down
3 changes: 3 additions & 0 deletions json_serializable/lib/src/type_helpers/config_types.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ class KeyConfig {

final String? readValueFunctionName;

final bool extra;

KeyConfig({
required this.defaultValue,
required this.disallowNullValue,
Expand All @@ -32,6 +34,7 @@ class KeyConfig {
required this.readValueFunctionName,
required this.required,
required this.unknownEnumValue,
required this.extra,
});
}

Expand Down