Skip to content

Improve toJson generation with some no-write null fields #33

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

Merged
merged 3 commits into from
Aug 21, 2017
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 0.2.3

* Write out `toJson` methods more efficiently when the first fields written are
not intercepted by the null-checking method.

## 0.2.2+1

* Simplify the serialization of `Map` instances when no conversion is required
Expand Down
39 changes: 21 additions & 18 deletions example/example.g.dart

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

40 changes: 29 additions & 11 deletions lib/src/json_serializable_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -151,15 +151,13 @@ class JsonSerializableGenerator
Map<String, FieldElement> fields, bool includeIfNull) {
buffer.writeln('{');

// TODO(kevmoo) We could write all values up to the null-excluded value
// directly in this literal.
buffer.writeln('var $toJsonMapVarName = <String, dynamic>{};');
buffer.writeln('var $toJsonMapVarName = <String, dynamic>{');

buffer.writeln('''void $toJsonMapHelperName(String key, dynamic value) {
if (value != null) {
$toJsonMapVarName[key] = value;
}
}''');
// Note that the map literal is left open above. As long as target fields
// don't need to be intercepted by the `only if null` logic, write them
// to the map literal directly. In theory, should allow more efficient
// serialization.
var directWrite = true;

fields.forEach((fieldName, field) {
try {
Expand All @@ -173,9 +171,29 @@ class JsonSerializableGenerator
}

if (_includeIfNull(field, includeIfNull)) {
buffer.writeln('$toJsonMapVarName[$safeJsonKeyString] = '
'${_serialize(field.type, fieldName, _nullable(field))};');
if (directWrite) {
buffer.writeln('$safeJsonKeyString : '
'${_serialize(field.type, fieldName, _nullable(field))},');
} else {
buffer.writeln('$toJsonMapVarName[$safeJsonKeyString] = '
'${_serialize(field.type, fieldName, _nullable(field))};');
}
} else {
if (directWrite) {
// close the still-open map literal
buffer.writeln('};');
buffer.writeln();

// write the helper to be used by all following null-excluding
// fields
buffer.writeln('''
void $toJsonMapHelperName(String key, dynamic value) {
if (value != null) {
$toJsonMapVarName[key] = value;
}
}''');
directWrite = false;
}
buffer.writeln('$toJsonMapHelperName($safeJsonKeyString, '
'${_serialize(field.type, fieldName, _nullable(field))});');
}
Expand All @@ -187,7 +205,7 @@ class JsonSerializableGenerator
}
});

buffer.writeln(r'return $map;');
buffer.writeln('return $toJsonMapVarName;');

buffer.writeln('}');
}
Expand Down
4 changes: 2 additions & 2 deletions lib/src/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,5 @@ void log(object) {
stderr.writeln(['***', object, '***'].join('\n'));
}

final toJsonMapVarName = r'$map';
final toJsonMapHelperName = r'$writeNotNull';
final toJsonMapVarName = 'val';
final toJsonMapHelperName = 'writeNotNull';
3 changes: 2 additions & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: json_serializable
version: 0.2.2+1
version: 0.2.3-dev
author: Dart Team <misc@dartlang.org>
description: Generates utilities to aid in serializing to/from JSON.
homepage: https://github.com/dart-lang/json_serializable
Expand All @@ -15,4 +15,5 @@ dev_dependencies:
build_runner: ^0.4.0
build_test: ^0.6.0
collection: ^1.14.0
dart_style: ^1.0.0
test: ^0.12.3
73 changes: 61 additions & 12 deletions test/json_serializable_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import 'dart:async';

import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/src/string_source.dart';
import 'package:dart_style/dart_style.dart' as dart_style;
import 'package:json_serializable/generators.dart';
import 'package:json_serializable/src/utils.dart';
import 'package:path/path.dart' as p;
Expand Down Expand Up @@ -99,21 +100,65 @@ void main() {
});

group('valid inputs', () {
test('class with no fields', () async {
test('class with no ctor params', () async {
var output = await _runForElementNamed('Person');

expect(output, isNotNull);

// TODO: test the actual output
// print(output);
expect(output,
r'''Person _$PersonFromJson(Map<String, dynamic> json) => new Person()
..firstName = json['firstName'] as String
..lastName = json['lastName'] as String
..height = json['h'] as int
..dateOfBirth = json['dateOfBirth'] == null
? null
: DateTime.parse(json['dateOfBirth'] as String)
..dynamicType = json['dynamicType']
..varType = json['varType']
..listOfInts = (json['listOfInts'] as List)?.map((e) => e as int)?.toList();

abstract class _$PersonSerializerMixin {
String get firstName;
String get lastName;
int get height;
DateTime get dateOfBirth;
dynamic get dynamicType;
dynamic get varType;
List<int> get listOfInts;
Map<String, dynamic> toJson() => <String, dynamic>{
'firstName': firstName,
'lastName': lastName,
'h': height,
'dateOfBirth': dateOfBirth?.toIso8601String(),
'dynamicType': dynamicType,
'varType': varType,
'listOfInts': listOfInts
};
}
''');
});

test('class with ctor params', () async {
var output = await _runForElementNamed('Order');
expect(output, isNotNull);

// TODO: test the actual output
// print(output);
expect(output,
r'''Order _$OrderFromJson(Map<String, dynamic> json) => new Order(
json['height'] as int,
json['firstName'] as String,
json['lastName'] as String)
..dateOfBirth = json['dateOfBirth'] == null
? null
: DateTime.parse(json['dateOfBirth'] as String);

abstract class _$OrderSerializerMixin {
String get firstName;
String get lastName;
int get height;
DateTime get dateOfBirth;
Map<String, dynamic> toJson() => <String, dynamic>{
'firstName': firstName,
'lastName': lastName,
'height': height,
'dateOfBirth': dateOfBirth?.toIso8601String()
};
}
''');
});

test('class with child json-able object', () async {
Expand Down Expand Up @@ -159,20 +204,24 @@ void main() {

test('all', () async {
var output = await _runForElementNamed('IncludeIfNullOverride');
expect(output, contains("$toJsonMapVarName[\'number\'] = number;"));
expect(output, contains("'number': number,"));
expect(output, contains("$toJsonMapHelperName('str', str);"));
});
});
}

const _generator = const JsonSerializableGenerator();

final _formatter = new dart_style.DartFormatter();

Future<String> _runForElementNamed(String name) async {
var library = new LibraryReader(_compUnit.element.library);
var element = library.allElements.singleWhere((e) => e.name == name);
var annotation = _generator.typeChecker.firstAnnotationOf(element);
return _generator.generateForAnnotatedElement(
var generated = await _generator.generateForAnnotatedElement(
element, new ConstantReader(annotation), null);

return _formatter.format(generated);
}

Future<CompilationUnit> _getCompilationUnitForString(String projectPath) async {
Expand Down
2 changes: 1 addition & 1 deletion test/kitchen_sink_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ void _sharedTests(
..dateTime = new DateTime.now()
..dateTimeList = []
..crazyComplex = []
..$map = {};
..val = {};

var json = item.toJson();
expect(json.keys, orderedEquals(_expectedOrder));
Expand Down
4 changes: 2 additions & 2 deletions test/test_files/bathtub.dart
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,9 @@ class Bathtub extends Object

// Handle fields with names that collide with helper names
@JsonKey(nullable: false, includeIfNull: false)
Map<String, bool> $map = {};
Map<String, bool> val = {};
@JsonKey(nullable: false)
bool $writeNotNull;
bool writeNotNull;
@JsonKey(nullable: false, name: r'$string')
String string;

Expand Down
79 changes: 40 additions & 39 deletions test/test_files/bathtub.g.dart

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

4 changes: 2 additions & 2 deletions test/test_files/kitchen_sink.dart
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@ class KitchenSink extends Object with _$KitchenSinkSerializerMixin {

// Handle fields with names that collide with helper names
@JsonKey(includeIfNull: false)
Map<String, bool> $map;
bool $writeNotNull;
Map<String, bool> val;
bool writeNotNull;
@JsonKey(name: r'$string')
String string;

Expand Down
Loading