Skip to content

Commit 7d71de0

Browse files
committed
Allow extra non-serializable fields to be passed into fromJson and ignored in toJson
Document JsonKey.extra Add extra key to KeyConfig class Fixes
1 parent f50eb99 commit 7d71de0

File tree

5 files changed

+52
-13
lines changed

5 files changed

+52
-13
lines changed

json_annotation/lib/src/json_key.dart

+10
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,15 @@ class JsonKey {
112112
/// valid on a nullable enum field.
113113
final Enum? unknownEnumValue;
114114

115+
/// `true` if this field should not be serialized.
116+
///
117+
/// Fields annotated with [extra] set to true will not be included in JSON
118+
/// output, and their values may be passed in as additional arguments
119+
/// alongside JSON data in the deserialization function.
120+
///
121+
/// If `null` (the default) or false, this argument has no effect.
122+
final bool? extra;
123+
115124
/// Creates a new [JsonKey] instance.
116125
///
117126
/// Only required when the default behavior is not desired.
@@ -127,6 +136,7 @@ class JsonKey {
127136
this.required,
128137
this.toJson,
129138
this.unknownEnumValue,
139+
this.extra,
130140
});
131141

132142
/// Sentinel value for use with [unknownEnumValue].

json_serializable/lib/src/decode_helper.dart

+22-6
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ abstract class DecodeHelper implements HelperCore {
2525
CreateFactoryResult createFactory(
2626
Map<String, FieldElement> accessibleFields,
2727
Map<String, String> unavailableReasons,
28+
List<FieldElement> extras,
2829
) {
2930
assert(config.createFactory);
3031
final buffer = StringBuffer();
@@ -47,6 +48,16 @@ abstract class DecodeHelper implements HelperCore {
4748
}
4849
}
4950

51+
if (extras.isNotEmpty) {
52+
buffer.writeln(', {');
53+
for (final extra in extras) {
54+
if (!extra.type.isNullableType) buffer.write('required ');
55+
buffer.writeln(
56+
'${extra.type.getDisplayString(withNullability: true)} ${extra.name},');
57+
}
58+
buffer.write('}');
59+
}
60+
5061
buffer.write(')');
5162

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

70+
final extraNames = [for (final extra in extras) extra.name];
5971
final data = _writeConstructorInvocation(
6072
element,
6173
config.constructor,
62-
accessibleFields.keys,
74+
[...accessibleFields.keys, ...extraNames],
6375
accessibleFields.values
6476
.where((fe) => element.lookUpSetter(fe.name, element.library) != null)
6577
.map((fe) => fe.name)
6678
.toList(),
6779
unavailableReasons,
80+
extraNames,
6881
deserializeFun,
6982
);
7083

@@ -265,6 +278,7 @@ _ConstructorData _writeConstructorInvocation(
265278
Iterable<String> availableConstructorParameters,
266279
Iterable<String> writableFields,
267280
Map<String, String> unavailableReasons,
281+
List<String> extras,
268282
String Function(String paramOrFieldName, {ParameterElement ctorParam})
269283
deserializeForField,
270284
) {
@@ -301,7 +315,7 @@ _ConstructorData _writeConstructorInvocation(
301315
} else {
302316
constructorArguments.add(arg);
303317
}
304-
usedCtorParamsAndFields.add(arg.name);
318+
if (!extras.contains(arg.name)) usedCtorParamsAndFields.add(arg.name);
305319
}
306320

307321
// fields that aren't already set by the constructor and that aren't final
@@ -320,17 +334,19 @@ _ConstructorData _writeConstructorInvocation(
320334
buffer
321335
..writeln()
322336
..writeAll(constructorArguments.map((paramElement) {
323-
final content =
324-
deserializeForField(paramElement.name, ctorParam: paramElement);
337+
final content = extras.contains(paramElement.name)
338+
? paramElement.name
339+
: deserializeForField(paramElement.name, ctorParam: paramElement);
325340
return ' $content,\n';
326341
}));
327342
}
328343
if (namedConstructorArguments.isNotEmpty) {
329344
buffer
330345
..writeln()
331346
..writeAll(namedConstructorArguments.map((paramElement) {
332-
final value =
333-
deserializeForField(paramElement.name, ctorParam: paramElement);
347+
final value = extras.contains(paramElement.name)
348+
? paramElement.name
349+
: deserializeForField(paramElement.name, ctorParam: paramElement);
334350
return ' ${paramElement.name}: $value,\n';
335351
}));
336352
}

json_serializable/lib/src/generator_helper.dart

+14-7
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ class GeneratorHelper extends HelperCore with EncodeHelper, DecodeHelper {
5858
// these fields.
5959
final unavailableReasons = <String, String>{};
6060

61+
final extras = <FieldElement>[];
62+
6163
final accessibleFields = sortedFields.fold<Map<String, FieldElement>>(
6264
<String, FieldElement>{},
6365
(map, field) {
@@ -68,21 +70,26 @@ class GeneratorHelper extends HelperCore with EncodeHelper, DecodeHelper {
6870
unavailableReasons[field.name] =
6971
'Setter-only properties are not supported.';
7072
log.warning('Setters are ignored: ${element.name}.${field.name}');
71-
} else if (jsonKeyFor(field).ignore) {
72-
unavailableReasons[field.name] =
73-
'It is assigned to an ignored field.';
7473
} else {
75-
assert(!map.containsKey(field.name));
76-
map[field.name] = field;
74+
final jsonKey = jsonKeyFor(field);
75+
if (jsonKey.ignore) {
76+
unavailableReasons[field.name] =
77+
'It is assigned to an ignored field.';
78+
} else if (jsonKey.extra) {
79+
extras.add(field);
80+
} else {
81+
assert(!map.containsKey(field.name));
82+
map[field.name] = field;
83+
}
7784
}
78-
7985
return map;
8086
},
8187
);
8288

8389
var accessibleFieldSet = accessibleFields.values.toSet();
8490
if (config.createFactory) {
85-
final createResult = createFactory(accessibleFields, unavailableReasons);
91+
final createResult =
92+
createFactory(accessibleFields, unavailableReasons, extras);
8693
yield createResult.output;
8794

8895
accessibleFieldSet = accessibleFields.entries

json_serializable/lib/src/json_key_utils.dart

+3
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,7 @@ KeyConfig _from(FieldElement element, ClassConfig classAnnotation) {
227227
readValueFunctionName: readValueFunctionName,
228228
required: obj.read('required').literalValue as bool?,
229229
unknownEnumValue: _annotationValue('unknownEnumValue', mustBeEnum: true),
230+
extra: obj.read('extra').literalValue as bool?,
230231
);
231232
}
232233

@@ -241,6 +242,7 @@ KeyConfig _populateJsonKey(
241242
String? readValueFunctionName,
242243
bool? required,
243244
String? unknownEnumValue,
245+
bool? extra,
244246
}) {
245247
if (disallowNullValue == true) {
246248
if (includeIfNull == true) {
@@ -261,6 +263,7 @@ KeyConfig _populateJsonKey(
261263
readValueFunctionName: readValueFunctionName,
262264
required: required ?? false,
263265
unknownEnumValue: unknownEnumValue,
266+
extra: extra ?? false,
264267
);
265268
}
266269

json_serializable/lib/src/type_helpers/config_types.dart

+3
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ class KeyConfig {
2323

2424
final String? readValueFunctionName;
2525

26+
final bool extra;
27+
2628
KeyConfig({
2729
required this.defaultValue,
2830
required this.disallowNullValue,
@@ -32,6 +34,7 @@ class KeyConfig {
3234
required this.readValueFunctionName,
3335
required this.required,
3436
required this.unknownEnumValue,
37+
required this.extra,
3538
});
3639
}
3740

0 commit comments

Comments
 (0)