Skip to content

Commit 86098bc

Browse files
committed
Add includeIfNull to JsonSerialiazable annotation
1 parent 102f596 commit 86098bc

File tree

3 files changed

+65
-19
lines changed

3 files changed

+65
-19
lines changed

lib/src/json_serializable.dart

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,17 @@ class JsonSerializable {
66
final bool createFactory;
77
final bool createToJson;
88

9-
const JsonSerializable({bool createFactory: true, bool createToJson: true})
10-
: this.createFactory = createFactory,
11-
this.createToJson = createToJson;
9+
/// Whether the generator should include the this field in the serialized
10+
/// output, even if the value is `null`.
11+
final bool includeIfNull;
12+
13+
const JsonSerializable(
14+
{bool createFactory: true,
15+
bool createToJson: true,
16+
bool includeIfNull: true})
17+
: this.createFactory = createFactory ?? true,
18+
this.createToJson = createToJson ?? true,
19+
this.includeIfNull = includeIfNull ?? true;
1220
}
1321

1422
/// An annotation used to specify how a field is serialized.
@@ -29,12 +37,16 @@ class JsonKey {
2937

3038
/// [true] if the generator should include the this field in the serialized
3139
/// output, even if the value is `null`.
40+
///
41+
/// The default value, `null`, indicates that the behavior should be
42+
/// acquired from the [JsonSerializable.includeIfNull] annotation on the
43+
/// enclosing class.
3244
final bool includeIfNull;
3345

3446
/// Creates a new [JsonKey].
3547
///
3648
/// Only required when the default behavior is not desired.
37-
const JsonKey({this.name, bool nullable: true, bool includeIfNull: true})
49+
const JsonKey({this.name, bool nullable: true, bool includeIfNull})
3850
: this.nullable = nullable ?? true,
39-
this.includeIfNull = includeIfNull ?? true;
51+
this.includeIfNull = includeIfNull;
4052
}

lib/src/json_serializable_generator.dart

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -128,12 +128,13 @@ class JsonSerializableGenerator
128128
});
129129

130130
buffer.writeln(' Map<String, dynamic> toJson() ');
131-
if (fieldsList.every((fe) => _jsonKeyFor(fe).includeIfNull)) {
131+
if (fieldsList
132+
.every((e) => _includeIfNull(e, annotation.includeIfNull))) {
132133
// write simple `toJson` method that includes all keys...
133134
_writeToJsonSimple(buffer, fields);
134135
} else {
135136
// At least one field should be excluded if null
136-
_writeToJsonWithNullChecks(buffer, fields);
137+
_writeToJsonWithNullChecks(buffer, fields, annotation.includeIfNull);
137138
}
138139

139140
// end of the mixin class
@@ -143,8 +144,8 @@ class JsonSerializableGenerator
143144
return buffer.toString();
144145
}
145146

146-
void _writeToJsonWithNullChecks(
147-
StringBuffer buffer, Map<String, FieldElement> fields) {
147+
void _writeToJsonWithNullChecks(StringBuffer buffer,
148+
Map<String, FieldElement> fields, bool includeIfNull) {
148149
buffer.writeln('{');
149150

150151
// TODO(kevmoo) We could write all values up to the null-excluded value
@@ -159,21 +160,21 @@ class JsonSerializableGenerator
159160

160161
fields.forEach((fieldName, field) {
161162
try {
162-
var jsonKey = _jsonKeyFor(field);
163-
var safeJsonKeyString = _safeNameAccess(jsonKey.name ?? fieldName);
163+
var safeJsonKeyString =
164+
_safeNameAccess(_fieldToJsonMapKey(field, fieldName));
164165

165166
// If `fieldName` collides with one of the local helpers, prefix
166167
// access with `this.`.
167168
if (fieldName == toJsonMapVarName || fieldName == toJsonMapHelperName) {
168169
fieldName = 'this.$fieldName';
169170
}
170171

171-
if (jsonKey.includeIfNull) {
172+
if (_includeIfNull(field, includeIfNull)) {
172173
buffer.writeln("$toJsonMapVarName[$safeJsonKeyString] = "
173-
"${_serialize(field.type, fieldName, jsonKey.nullable)};");
174+
"${_serialize(field.type, fieldName, _nullable(field))};");
174175
} else {
175-
buffer.writeln("$toJsonMapHelperName($safeJsonKeyString,"
176-
"${_serialize(field.type, fieldName, jsonKey.nullable)});");
176+
buffer.writeln("$toJsonMapHelperName($safeJsonKeyString, "
177+
"${_serialize(field.type, fieldName, _nullable(field))});");
177178
}
178179
} on UnsupportedTypeError {
179180
throw new InvalidGenerationSourceError(
@@ -195,7 +196,7 @@ class JsonSerializableGenerator
195196
var pairs = <String>[];
196197
fields.forEach((fieldName, field) {
197198
try {
198-
pairs.add("'${_fieldToJsonMapKey(field) ?? fieldName}': "
199+
pairs.add("'${_fieldToJsonMapKey(field, fieldName)}': "
199200
"${_serialize(field.type, fieldName, _nullable(field))}");
200201
} on UnsupportedTypeError {
201202
throw new InvalidGenerationSourceError(
@@ -321,7 +322,7 @@ class JsonSerializableGenerator
321322

322323
String _deserializeForField(String name, FieldElement field,
323324
{ParameterElement ctorParam}) {
324-
name = _fieldToJsonMapKey(field) ?? name;
325+
name = _fieldToJsonMapKey(field, name);
325326

326327
var targetType = ctorParam?.type ?? field.type;
327328

@@ -350,13 +351,17 @@ String _safeNameAccess(String name) =>
350351
/// Returns the JSON map `key` to be used when (de)serializing [field], if any.
351352
///
352353
/// Otherwise, `null`;
353-
String _fieldToJsonMapKey(FieldElement field) => _jsonKeyFor(field).name;
354+
String _fieldToJsonMapKey(FieldElement field, String ifNull) =>
355+
_jsonKeyFor(field).name ?? ifNull;
354356

355357
/// Returns `true` if the field should be treated as potentially nullable.
356358
///
357359
/// If no [JsonKey] annotation is present on the field, `true` is returned.
358360
bool _nullable(FieldElement field) => _jsonKeyFor(field).nullable;
359361

362+
bool _includeIfNull(FieldElement element, bool parentValue) =>
363+
_jsonKeyFor(element).includeIfNull ?? parentValue;
364+
360365
JsonKey _jsonKeyFor(FieldElement element) {
361366
var key = _jsonKeyExpando[element];
362367

test/json_serializable_test.dart

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import 'dart:async';
1010
import 'package:analyzer/dart/ast/ast.dart';
1111
import 'package:analyzer/src/string_source.dart';
1212
import 'package:json_serializable/generators.dart';
13+
import 'package:json_serializable/src/utils.dart';
1314
import 'package:path/path.dart' as p;
1415
import 'package:test/test.dart';
1516

@@ -153,11 +154,25 @@ void main() {
153154
expect(output, contains("'h': height,"));
154155
expect(output, contains("..height = json['h']"));
155156
});
157+
158+
group("includeIfNull", () {
159+
test("some", () async {
160+
var output = await _runForElementNamed('IncludeIfNullAll');
161+
expect(output, isNot(contains(toJsonMapVarName)));
162+
expect(output, isNot(contains(toJsonMapHelperName)));
163+
});
164+
165+
test("all", () async {
166+
var output = await _runForElementNamed('IncludeIfNullOverride');
167+
expect(output, contains("$toJsonMapVarName[\'number\'] = number;"));
168+
expect(output, contains("$toJsonMapHelperName('str', str);"));
169+
});
170+
});
156171
}
157172

158173
const _generator = const JsonSerializableGenerator();
159174

160-
Future<String> _runForElementNamed(String name) async {
175+
Future<String> _runForElementNamed(String name) {
161176
var library = _compUnit.element.library;
162177
var element =
163178
getElementsFromLibraryElement(library).singleWhere((e) => e.name == name);
@@ -278,4 +293,18 @@ class NoSerializeBadKey {
278293
class NoDeserializeBadKey {
279294
Map<int, DateTime> intDateTimeMap;
280295
}
296+
297+
@JsonSerializable(createFactory: false)
298+
class IncludeIfNullAll {
299+
@JsonKey(includeIfNull: true)
300+
int number;
301+
String str;
302+
}
303+
304+
@JsonSerializable(createFactory: false, includeIfNull: false)
305+
class IncludeIfNullOverride {
306+
@JsonKey(includeIfNull: true)
307+
int number;
308+
String str;
309+
}
281310
''';

0 commit comments

Comments
 (0)