Skip to content

Commit 889e01e

Browse files
authored
Support inherited fields (#107)
1 parent 3360abb commit 889e01e

9 files changed

+211
-25
lines changed

json_serializable/CHANGELOG.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
## 0.3.3
1+
## 0.4.0
2+
3+
* **Potentially Breaking** Inherited fields are now processed and used
4+
when generating serialization and deserialization code. There is a possibility
5+
that the generated code may change in undesired ways for classes annotated for
6+
`v0.3`.
27

38
* Avoid unnecessary braces in string escapes.
49

json_serializable/lib/src/json_serializable_generator.dart

Lines changed: 59 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ import 'dart:collection';
77

88
import 'package:analyzer/dart/element/element.dart';
99
import 'package:analyzer/dart/element/type.dart';
10+
// ignore: implementation_imports
11+
import 'package:analyzer/src/dart/resolver/inheritance_manager.dart'
12+
show InheritanceManager;
1013
import 'package:analyzer/analyzer.dart';
1114
import 'package:json_annotation/json_annotation.dart';
1215
import 'package:source_gen/source_gen.dart';
@@ -75,11 +78,10 @@ class JsonSerializableGenerator
7578
}
7679

7780
var classElement = element as ClassElement;
78-
var className = classElement.name;
7981

8082
// Get all of the fields that need to be assigned
8183
// TODO: support overriding the field set with an annotation option
82-
var fieldsList = classElement.fields.where((e) => !e.isStatic).toList();
84+
var fieldsList = _listFields(classElement);
8385

8486
var undefinedFields =
8587
fieldsList.where((fe) => fe.type.isUndefined).toList();
@@ -94,7 +96,7 @@ class JsonSerializableGenerator
9496

9597
// Sort these in the order in which they appear in the class
9698
// Sadly, `classElement.fields` puts properties after fields
97-
fieldsList.sort((a, b) => _offsetFor(a).compareTo(_offsetFor(b)));
99+
fieldsList.sort(_sortByLocation);
98100

99101
// Explicitly using `LinkedHashMap` – we want these ordered.
100102
var fields = new LinkedHashMap<String, FieldElement>.fromIterable(
@@ -103,7 +105,7 @@ class JsonSerializableGenerator
103105

104106
// Get the constructor to use for the factory
105107

106-
var prefix = '_\$$className';
108+
var prefix = '_\$${classElement.name}';
107109

108110
var buffer = new StringBuffer();
109111

@@ -535,14 +537,37 @@ final _jsonKeyExpando = new Expando<JsonKey>();
535537

536538
final _jsonKeyChecker = new TypeChecker.fromRuntime(JsonKey);
537539

538-
/// Returns the offset of given field/property in its source file – with a
539-
/// preference for the getter if it's defined.
540-
int _offsetFor(FieldElement e) {
541-
if (e.getter != null && e.getter.nameOffset != e.nameOffset) {
542-
assert(e.nameOffset == -1);
543-
return e.getter.nameOffset;
540+
final _dartCoreObjectChecker = new TypeChecker.fromRuntime(Object);
541+
542+
int _sortByLocation(FieldElement a, FieldElement b) {
543+
var checkerA = new TypeChecker.fromStatic(a.enclosingElement.type);
544+
545+
if (!checkerA.isExactly(b.enclosingElement)) {
546+
// in this case, you want to prioritize the enclosingElement that is more
547+
// "super".
548+
549+
if (checkerA.isSuperOf(b.enclosingElement)) {
550+
return -1;
551+
}
552+
553+
var checkerB = new TypeChecker.fromStatic(b.enclosingElement.type);
554+
555+
if (checkerB.isSuperOf(a.enclosingElement)) {
556+
return 1;
557+
}
558+
}
559+
560+
/// Returns the offset of given field/property in its source file – with a
561+
/// preference for the getter if it's defined.
562+
int _offsetFor(FieldElement e) {
563+
if (e.getter != null && e.getter.nameOffset != e.nameOffset) {
564+
assert(e.nameOffset == -1);
565+
return e.getter.nameOffset;
566+
}
567+
return e.nameOffset;
544568
}
545-
return e.nameOffset;
569+
570+
return _offsetFor(a).compareTo(_offsetFor(b));
546571
}
547572

548573
final _notSupportedWithTypeHelpersMsg =
@@ -558,3 +583,26 @@ InvalidGenerationSourceError _createInvalidGenerationError(
558583
return new InvalidGenerationSourceError(message,
559584
todo: 'Make sure all of the types are serializable.');
560585
}
586+
587+
/// Returns a list of all instance, [FieldElement] items for [element] and
588+
/// super classes.
589+
List<FieldElement> _listFields(ClassElement element) {
590+
// Get all of the fields that need to be assigned
591+
// TODO: support overriding the field set with an annotation option
592+
var fieldsList = element.fields.where((e) => !e.isStatic).toList();
593+
594+
var manager = new InheritanceManager(element.library);
595+
596+
for (var v in manager.getMembersInheritedFromClasses(element).values) {
597+
assert(v is! FieldElement);
598+
if (_dartCoreObjectChecker.isExactly(v.enclosingElement)) {
599+
continue;
600+
}
601+
602+
if (v is PropertyAccessorElement && v.variable is FieldElement) {
603+
fieldsList.add(v.variable as FieldElement);
604+
}
605+
}
606+
607+
return fieldsList;
608+
}

json_serializable/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: json_serializable
2-
version: 0.3.3-dev
2+
version: 0.4.0-dev
33
author: Dart Team <misc@dartlang.org>
44
description: Generates utilities to aid in serializing to/from JSON.
55
homepage: https://github.com/dart-lang/json_serializable

json_serializable/test/json_serializable_test.dart

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,92 @@ abstract class _$OrderSerializerMixin {
274274
(e) => e.message,
275275
'The class `NoCtorClass` has no default constructor.')));
276276
});
277+
278+
test('super types', () async {
279+
var output = await runForElementNamed('SubType');
280+
281+
String expected;
282+
if (generator.useWrappers) {
283+
expected =
284+
r'''SubType _$SubTypeFromJson(Map<String, dynamic> json) => new SubType(
285+
json['subTypeViaCtor'] as int, json['super-type-via-ctor'] as int)
286+
..superTypeReadWrite = json['superTypeReadWrite'] as int
287+
..subTypeReadWrite = json['subTypeReadWrite'] as int;
288+
289+
abstract class _$SubTypeSerializerMixin {
290+
int get superTypeViaCtor;
291+
int get superTypeReadWrite;
292+
int get subTypeViaCtor;
293+
int get subTypeReadWrite;
294+
Map<String, dynamic> toJson() => new _$SubTypeJsonMapWrapper(this);
295+
}
296+
297+
class _$SubTypeJsonMapWrapper extends $JsonMapWrapper {
298+
final _$SubTypeSerializerMixin _v;
299+
_$SubTypeJsonMapWrapper(this._v);
300+
301+
@override
302+
Iterable<String> get keys sync* {
303+
yield 'super-type-via-ctor';
304+
if (_v.superTypeReadWrite != null) {
305+
yield 'superTypeReadWrite';
306+
}
307+
yield 'subTypeViaCtor';
308+
yield 'subTypeReadWrite';
309+
}
310+
311+
@override
312+
dynamic operator [](Object key) {
313+
if (key is String) {
314+
switch (key) {
315+
case 'super-type-via-ctor':
316+
return _v.superTypeViaCtor;
317+
case 'superTypeReadWrite':
318+
return _v.superTypeReadWrite;
319+
case 'subTypeViaCtor':
320+
return _v.subTypeViaCtor;
321+
case 'subTypeReadWrite':
322+
return _v.subTypeReadWrite;
323+
}
324+
}
325+
return null;
326+
}
327+
}
328+
''';
329+
} else {
330+
expected =
331+
r'''SubType _$SubTypeFromJson(Map<String, dynamic> json) => new SubType(
332+
json['subTypeViaCtor'] as int, json['super-type-via-ctor'] as int)
333+
..superTypeReadWrite = json['superTypeReadWrite'] as int
334+
..subTypeReadWrite = json['subTypeReadWrite'] as int;
335+
336+
abstract class _$SubTypeSerializerMixin {
337+
int get superTypeViaCtor;
338+
int get superTypeReadWrite;
339+
int get subTypeViaCtor;
340+
int get subTypeReadWrite;
341+
Map<String, dynamic> toJson() {
342+
var val = <String, dynamic>{
343+
'super-type-via-ctor': superTypeViaCtor,
344+
};
345+
346+
void writeNotNull(String key, dynamic value) {
347+
if (value != null) {
348+
val[key] = value;
349+
}
350+
}
351+
352+
writeNotNull('superTypeReadWrite', superTypeReadWrite);
353+
val['subTypeViaCtor'] = subTypeViaCtor;
354+
val['subTypeReadWrite'] = subTypeReadWrite;
355+
return val;
356+
}
357+
}
358+
''';
359+
}
360+
361+
expect(output, expected);
362+
});
277363
}
278364

279365
final _formatter = new dart_style.DartFormatter();

json_serializable/test/src/json_serializable_test_input.dart

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,3 +147,30 @@ class DupeKeys {
147147
@JsonKey(name: 'a')
148148
String str;
149149
}
150+
151+
@JsonSerializable()
152+
class SubType extends SuperType {
153+
final int subTypeViaCtor;
154+
int subTypeReadWrite;
155+
156+
SubType(this.subTypeViaCtor, int superTypeViaCtor) : super(superTypeViaCtor);
157+
}
158+
159+
// NOTE: `SuperType` is intentionally after `SubType` in the source file to
160+
// validate field ordering semantics.
161+
class SuperType {
162+
@JsonKey(name: 'super-type-via-ctor', nullable: false)
163+
final int superTypeViaCtor;
164+
165+
@JsonKey(includeIfNull: false)
166+
int superTypeReadWrite;
167+
168+
SuperType(this.superTypeViaCtor);
169+
170+
/// Add a property to try to throw-off the generator
171+
int get priceHalf => priceFraction(2);
172+
173+
/// Add a method to try to throw-off the generator
174+
int priceFraction(int other) =>
175+
superTypeViaCtor == null ? null : superTypeViaCtor ~/ other;
176+
}

json_serializable/test/test_files/json_test_example.dart

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,14 +66,13 @@ class Order extends Object with _$OrderSerializerMixin {
6666
}
6767

6868
@JsonSerializable()
69-
class Item extends Object with _$ItemSerializerMixin {
70-
final int price;
69+
class Item extends ItemCore with _$ItemSerializerMixin {
7170
@JsonKey(includeIfNull: false, name: 'item-number')
7271
int itemNumber;
7372
List<DateTime> saleDates;
7473
List<int> rates;
7574

76-
Item([this.price]);
75+
Item([int price]) : super(price);
7776

7877
factory Item.fromJson(Map<String, dynamic> json) => _$ItemFromJson(json);
7978

@@ -84,6 +83,12 @@ class Item extends Object with _$ItemSerializerMixin {
8483
_deepEquals(saleDates, other.saleDates);
8584
}
8685

86+
abstract class ItemCore {
87+
final int price;
88+
89+
ItemCore(this.price);
90+
}
91+
8792
bool _deepEquals(a, b) => const DeepCollectionEquality().equals(a, b);
8893

8994
class Platform {

json_serializable/test/test_files/json_test_example.non_nullable.dart

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,14 +72,13 @@ class Order extends Object with _$OrderSerializerMixin {
7272
}
7373

7474
@JsonSerializable(nullable: false)
75-
class Item extends Object with _$ItemSerializerMixin {
76-
final int price;
75+
class Item extends ItemCore with _$ItemSerializerMixin {
7776
@JsonKey(includeIfNull: false, name: 'item-number')
7877
int itemNumber;
7978
List<DateTime> saleDates;
8079
List<int> rates;
8180

82-
Item([this.price]);
81+
Item([int price]) : super(price);
8382

8483
factory Item.fromJson(Map<String, dynamic> json) => _$ItemFromJson(json);
8584

@@ -90,6 +89,12 @@ class Item extends Object with _$ItemSerializerMixin {
9089
_deepEquals(saleDates, other.saleDates);
9190
}
9291

92+
abstract class ItemCore {
93+
final int price;
94+
95+
ItemCore(this.price);
96+
}
97+
9398
bool _deepEquals(a, b) => const DeepCollectionEquality().equals(a, b);
9499

95100
class Platform {

json_serializable/test/test_files/json_test_example.non_nullable.wrapped.dart

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,14 +78,13 @@ class Order extends Object with _$OrderSerializerMixin {
7878
}
7979

8080
@JsonSerializable(nullable: false)
81-
class Item extends Object with _$ItemSerializerMixin {
82-
final int price;
81+
class Item extends ItemCore with _$ItemSerializerMixin {
8382
@JsonKey(includeIfNull: false, name: 'item-number')
8483
int itemNumber;
8584
List<DateTime> saleDates;
8685
List<int> rates;
8786

88-
Item([this.price]);
87+
Item([int price]) : super(price);
8988

9089
factory Item.fromJson(Map<String, dynamic> json) => _$ItemFromJson(json);
9190

@@ -96,6 +95,12 @@ class Item extends Object with _$ItemSerializerMixin {
9695
_deepEquals(saleDates, other.saleDates);
9796
}
9897

98+
abstract class ItemCore {
99+
final int price;
100+
101+
ItemCore(this.price);
102+
}
103+
99104
bool _deepEquals(a, b) => const DeepCollectionEquality().equals(a, b);
100105

101106
class Platform {

json_serializable/test/test_files/json_test_example.wrapped.dart

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,14 +72,13 @@ class Order extends Object with _$OrderSerializerMixin {
7272
}
7373

7474
@JsonSerializable()
75-
class Item extends Object with _$ItemSerializerMixin {
76-
final int price;
75+
class Item extends ItemCore with _$ItemSerializerMixin {
7776
@JsonKey(includeIfNull: false, name: 'item-number')
7877
int itemNumber;
7978
List<DateTime> saleDates;
8079
List<int> rates;
8180

82-
Item([this.price]);
81+
Item([int price]) : super(price);
8382

8483
factory Item.fromJson(Map<String, dynamic> json) => _$ItemFromJson(json);
8584

@@ -90,6 +89,12 @@ class Item extends Object with _$ItemSerializerMixin {
9089
_deepEquals(saleDates, other.saleDates);
9190
}
9291

92+
abstract class ItemCore {
93+
final int price;
94+
95+
ItemCore(this.price);
96+
}
97+
9398
bool _deepEquals(a, b) => const DeepCollectionEquality().equals(a, b);
9499

95100
class Platform {

0 commit comments

Comments
 (0)