Skip to content

Commit 23d0f3d

Browse files
committed
Added new generate_to_json_function build option
Controls how `toJson` functionality is generated for all types processed by this generator. If `false` (the default), a private `_$ClassNameSerializerMixin` class is created in the generated part file which contains a `toJson` method. Mix in this class to the source class: ```dart @JsonSerializable() class Example extends Object with _$ExampleSerializerMixin { // ... } ``` If `true`, then a top-level function is created that you can reference from your class. ```dart @JsonSerializable() class Example { // ... Map<String, dynamic> toJson() => _$ExampleToJson(this); } ```
1 parent fd874dd commit 23d0f3d

19 files changed

+233
-152
lines changed

json_annotation/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
`DisallowedNullValueException` that is thrown when corresponding keys exist in
1717
a source JSON map, but their values are `null`.
1818

19+
* Updated documentation of `JsonSerializable.createToJson` to include details
20+
of the new `generate_to_json_function` configuration option.
21+
1922
## 0.2.7+1
2023

2124
* Small improvement to `UnrecognizedKeysException.message` output and

json_annotation/lib/src/json_serializable.dart

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,31 @@ class JsonSerializable {
2525
/// ```
2626
final bool createFactory;
2727

28-
/// If `true` (the default), a private `_$ClassNameMixin` class is created
28+
/// If `true` (the default), code for decoding JSON is generated fon this
29+
/// class.
30+
///
31+
/// By default, a private `_$ClassNameMixin` class is created
2932
/// in the generated part file which contains a `toJson` method.
3033
///
3134
/// Mix in this class to the source class:
3235
///
3336
/// ```dart
3437
/// @JsonSerializable()
35-
/// class Example extends Object with _$ExampleMixin {
38+
/// class Example extends Object with _$ExampleSerializerMixin {
3639
/// // ...
3740
/// }
3841
/// ```
42+
///
43+
/// If `json_serializable` is configured with
44+
/// `generate_to_json_function: true`, then a top-level function is created
45+
/// that you can reference from your class.
46+
///
47+
/// ```dart
48+
/// @JsonSerializable()
49+
/// class Example {
50+
/// Map<String, dynamic> toJson() => _$ExampleToJson(this);
51+
/// }
52+
/// ```
3953
final bool createToJson;
4054

4155
/// Whether the generator should include fields with `null` values in the

json_serializable/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@
1818
`JsonSerializableGenerator.withDefaultHelpers` constructor.
1919

2020
* Added `explicit_to_json` configuration option.
21+
* See `JsonSerializableGenerator.explicitToJson` for details.
22+
23+
* Added `generate_to_json_function` configuration option.
24+
* See `JsonSerializableGenerator.generateToJsonFunction` for details.
2125

2226
## 0.5.6
2327

json_serializable/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ targets:
9393
any_map: true
9494
checked: true
9595
explicit_to_json: true
96+
generate_to_json_function: true
9697
```
9798
9899
[example]: https://github.com/dart-lang/json_serializable/blob/master/example

json_serializable/lib/builder.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ Builder jsonSerializable(BuilderOptions options) {
3131
checked: optionsMap.remove('checked') as bool,
3232
anyMap: optionsMap.remove('any_map') as bool,
3333
explicitToJson: optionsMap.remove('explicit_to_json') as bool,
34+
generateToJsonFunction:
35+
optionsMap.remove('generate_to_json_function') as bool,
3436
);
3537

3638
if (optionsMap.isNotEmpty) {

json_serializable/lib/src/generator_helper.dart

Lines changed: 74 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ Future<String> generate(JsonSerializableGenerator generator, Element element,
3333
}
3434

3535
class _GeneratorHelper {
36+
/// Name of the parameter used when generating top-level `toJson` functions
37+
/// if [JsonSerializableGenerator.generateToJsonFunction] is `true`.
38+
static const _toJsonParamName = 'instance';
39+
3640
final ClassElement _element;
3741
final JsonSerializable _annotation;
3842
final JsonSerializableGenerator _generator;
@@ -76,42 +80,7 @@ class _GeneratorHelper {
7680
return set;
7781
});
7882

79-
if (_annotation.createToJson) {
80-
//
81-
// Generate the mixin class
82-
//
83-
_buffer.writeln('abstract class ${_mixinClassName(true)} {');
84-
85-
// write copies of the fields - this allows the toJson method to access
86-
// the fields of the target class
87-
for (var field in accessibleFields) {
88-
//TODO - handle aliased imports
89-
_buffer.writeln(' ${field.type} get ${field.name};');
90-
}
91-
92-
_buffer.write(' Map<String, dynamic> toJson() ');
93-
94-
var writeNaive = accessibleFields.every(_writeJsonValueNaive);
95-
96-
if (_generator.useWrappers) {
97-
_buffer.writeln('=> new ${_wrapperClassName(false)}(this);');
98-
} else {
99-
if (writeNaive) {
100-
// write simple `toJson` method that includes all keys...
101-
_writeToJsonSimple(accessibleFields);
102-
} else {
103-
// At least one field should be excluded if null
104-
_writeToJsonWithNullChecks(accessibleFields);
105-
}
106-
}
107-
108-
// end of the mixin class
109-
_buffer.writeln('}');
110-
111-
if (_generator.useWrappers) {
112-
_writeWrapper(accessibleFields);
113-
}
114-
}
83+
_writeToJson(accessibleFields);
11584

11685
return _buffer.toString();
11786
}
@@ -274,12 +243,67 @@ class _GeneratorHelper {
274243
}
275244
}
276245

246+
void _writeToJson(Set<FieldElement> accessibleFields) {
247+
if (!_annotation.createToJson) {
248+
return;
249+
}
250+
251+
if (_generator.generateToJsonFunction) {
252+
var functionName = '${_prefix}ToJson${_genericClassArguments(true)}';
253+
_buffer.write('Map<String, dynamic> $functionName'
254+
'($_targetClassReference $_toJsonParamName) ');
255+
} else {
256+
//
257+
// Generate the mixin class
258+
//
259+
_buffer.writeln('abstract class ${_mixinClassName(true)} {');
260+
261+
// write copies of the fields - this allows the toJson method to access
262+
// the fields of the target class
263+
for (var field in accessibleFields) {
264+
//TODO - handle aliased imports
265+
_buffer.writeln(' ${field.type} get ${field.name};');
266+
}
267+
268+
_buffer.write(' Map<String, dynamic> toJson() ');
269+
}
270+
271+
var writeNaive = accessibleFields.every(_writeJsonValueNaive);
272+
273+
if (_generator.useWrappers) {
274+
var param = _generator.generateToJsonFunction ? _toJsonParamName : 'this';
275+
_buffer.writeln('=> new ${_wrapperClassName(false)}($param);');
276+
} else {
277+
if (writeNaive) {
278+
// write simple `toJson` method that includes all keys...
279+
_writeToJsonSimple(accessibleFields);
280+
} else {
281+
// At least one field should be excluded if null
282+
_writeToJsonWithNullChecks(accessibleFields);
283+
}
284+
}
285+
286+
if (!_generator.generateToJsonFunction) {
287+
// end of the mixin class
288+
_buffer.writeln('}');
289+
}
290+
291+
if (_generator.useWrappers) {
292+
_writeWrapper(accessibleFields);
293+
}
294+
}
295+
277296
void _writeWrapper(Iterable<FieldElement> fields) {
278297
_buffer.writeln();
279298
// TODO(kevmoo): write JsonMapWrapper if annotation lib is prefix-imported
299+
300+
var fieldType = _generator.generateToJsonFunction
301+
? _targetClassReference
302+
: _mixinClassName(false);
303+
280304
_buffer.writeln('''
281305
class ${_wrapperClassName(true)} extends \$JsonMapWrapper {
282-
final ${_mixinClassName(false)} _v;
306+
final $fieldType _v;
283307
${_wrapperClassName()}(this._v);
284308
''');
285309

@@ -334,6 +358,14 @@ class ${_wrapperClassName(true)} extends \$JsonMapWrapper {
334358
_buffer.writeln('}');
335359
}
336360

361+
String _fieldAccess(FieldElement field) {
362+
var fieldAccess = field.name;
363+
if (_generator.generateToJsonFunction) {
364+
fieldAccess = '$_toJsonParamName.$fieldAccess';
365+
}
366+
return fieldAccess;
367+
}
368+
337369
void _writeToJsonWithNullChecks(Iterable<FieldElement> fields) {
338370
_buffer.writeln('{');
339371

@@ -346,13 +378,15 @@ class ${_wrapperClassName(true)} extends \$JsonMapWrapper {
346378
var directWrite = true;
347379

348380
for (var field in fields) {
349-
var safeFieldAccess = field.name;
381+
var safeFieldAccess = _fieldAccess(field);
350382
var safeJsonKeyString = _safeNameAccess(field);
351383

352384
// If `fieldName` collides with one of the local helpers, prefix
353385
// access with `this.`.
354386
if (safeFieldAccess == generatedLocalVarName ||
355387
safeFieldAccess == toJsonMapHelperName) {
388+
assert(!_generator.generateToJsonFunction,
389+
'This code path should only be hit during the mixin codepath.');
356390
safeFieldAccess = 'this.$safeFieldAccess';
357391
}
358392

@@ -394,8 +428,9 @@ class ${_wrapperClassName(true)} extends \$JsonMapWrapper {
394428
_buffer.writeln('=> <String, dynamic>{');
395429

396430
_buffer.writeAll(fields.map((field) {
431+
var access = _fieldAccess(field);
397432
var value =
398-
'${_safeNameAccess(field)}: ${_serializeField(field, field.name)}';
433+
'${_safeNameAccess(field)}: ${_serializeField(field, access)}';
399434
return ' $value';
400435
}), ',\n');
401436

json_serializable/lib/src/json_part_builder.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,15 @@ Builder jsonPartBuilder({
2727
bool anyMap: false,
2828
bool checked: false,
2929
bool explicitToJson: false,
30+
bool generateToJsonFunction: false,
3031
}) {
3132
return new PartBuilder([
3233
new JsonSerializableGenerator(
3334
useWrappers: useWrappers,
3435
anyMap: anyMap,
3536
checked: checked,
3637
explicitToJson: explicitToJson,
38+
generateToJsonFunction: generateToJsonFunction,
3739
),
3840
const JsonLiteralGenerator()
3941
], header: header, formatOutput: formatOutput);

json_serializable/lib/src/json_serializable_generator.dart

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,33 @@ class JsonSerializableGenerator
8888
/// ```
8989
final bool explicitToJson;
9090

91+
/// Controls how `toJson` functionality is generated for all types processed
92+
/// by this generator.
93+
///
94+
/// If `false` (the default), a private `_$ClassNameSerializerMixin` class is
95+
/// created in the generated part file which contains a `toJson` method.
96+
///
97+
/// Mix in this class to the source class:
98+
///
99+
/// ```dart
100+
/// @JsonSerializable()
101+
/// class Example extends Object with _$ExampleSerializerMixin {
102+
/// // ...
103+
/// }
104+
/// ```
105+
///
106+
/// If `true`, then a top-level function is created that you can reference
107+
/// from your class.
108+
///
109+
/// ```dart
110+
/// @JsonSerializable()
111+
/// class Example {
112+
/// // ...
113+
/// Map<String, dynamic> toJson() => _$ExampleToJson(this);
114+
/// }
115+
/// ```
116+
final bool generateToJsonFunction;
117+
91118
/// Creates an instance of [JsonSerializableGenerator].
92119
///
93120
/// If [typeHelpers] is not provided, three built-in helpers are used:
@@ -98,10 +125,12 @@ class JsonSerializableGenerator
98125
bool anyMap: false,
99126
bool checked: false,
100127
bool explicitToJson: false,
128+
bool generateToJsonFunction: false,
101129
}) : this.useWrappers = useWrappers ?? false,
102130
this.anyMap = anyMap ?? false,
103131
this.checked = checked ?? false,
104132
this.explicitToJson = explicitToJson ?? false,
133+
this.generateToJsonFunction = generateToJsonFunction ?? false,
105134
this._typeHelpers = typeHelpers ?? _defaultHelpers;
106135

107136
/// Creates an instance of [JsonSerializableGenerator].
@@ -114,11 +143,13 @@ class JsonSerializableGenerator
114143
bool useWrappers: false,
115144
bool anyMap: false,
116145
bool checked: false,
146+
bool generateToJsonFunction: false,
117147
}) =>
118148
new JsonSerializableGenerator(
119149
useWrappers: useWrappers,
120150
anyMap: anyMap,
121151
checked: checked,
152+
generateToJsonFunction: generateToJsonFunction,
122153
typeHelpers:
123154
new List.unmodifiable(typeHelpers.followedBy(_defaultHelpers)));
124155

json_serializable/test/config_test.dart

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,13 +101,15 @@ const _validConfig = const {
101101
'use_wrappers': true,
102102
'any_map': true,
103103
'checked': true,
104-
'explicit_to_json': true
104+
'explicit_to_json': true,
105+
'generate_to_json_function': true,
105106
};
106107

107108
const _invalidConfig = const {
108109
'header': true,
109110
'use_wrappers': 42,
110111
'any_map': 42,
111112
'checked': 42,
112-
'explicit_to_json': 42
113+
'explicit_to_json': 42,
114+
'generate_to_json_function': 42,
113115
};

json_serializable/test/default_value/default_value.checked.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,7 @@ dvi.DefaultValue fromJson(Map<String, dynamic> json) =>
2323
_$DefaultValueFromJson(json);
2424

2525
@JsonSerializable()
26-
class DefaultValue extends Object
27-
with _$DefaultValueSerializerMixin
28-
implements dvi.DefaultValue {
26+
class DefaultValue implements dvi.DefaultValue {
2927
@JsonKey(defaultValue: true)
3028
bool fieldBool;
3129

@@ -62,4 +60,6 @@ class DefaultValue extends Object
6260

6361
factory DefaultValue.fromJson(Map<String, dynamic> json) =>
6462
_$DefaultValueFromJson(json);
63+
64+
Map<String, dynamic> toJson() => _$DefaultValueToJson(this);
6565
}

0 commit comments

Comments
 (0)