Skip to content

Commit 88fb50a

Browse files
authored
Improve JSON support for Iterable and List (#176)
* Add Iterable and List test examples * Remove gratuitous List optimization * JSON: Write abstract properties with generic types * JSON: Avoid gratuitous list conversion while deserializing
1 parent 3218e40 commit 88fb50a

File tree

5 files changed

+101
-24
lines changed

5 files changed

+101
-24
lines changed

example/example.g.dart

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/generators/json_serializable_generator.dart

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,7 @@ class JsonSerializableGenerator
8888
// write fields
8989
fields.forEach((name, field) {
9090
//TODO - handle aliased imports
91-
//TODO - write generic types. Now `List<int>` turns into `List`
92-
buffer.writeln(' ${field.type.name} get $name;');
91+
buffer.writeln(' ${field.type} get $name;');
9392
});
9493

9594
// write toJson method
@@ -204,26 +203,33 @@ class JsonSerializableGenerator
204203
}
205204
}
206205

207-
if (_coreListChecker.isAssignableFromType(fieldType)) {
208-
var indexVal = "i${depth}";
209-
210-
var substitute = '${expression}[$indexVal]';
206+
var isList = false;
207+
if (_coreIterableChecker.isAssignableFromType(fieldType)) {
208+
if (_coreListChecker.isAssignableFromType(fieldType)) {
209+
isList = true;
210+
}
211+
var substitute = "v$depth";
211212
var subFieldValue = _fieldToJsonMapValue(substitute,
212213
_getIterableGenericType(fieldType as InterfaceType), depth + 1);
213214

214-
// If we're dealing with `List<T>` where `T` must be serialized, then
215-
// generate a value that does the equivalent of .map(...).toList(), but
216-
// Does so efficiently by creating a known-length List.
217-
//TODO(kevmoo) this might be overkill. I think .map on iterable returns
218-
// an efficient-length iterable, so .map(...).toList() might be fine. :-/
215+
// In the case of trivial JSON types (int, String, etc), `subFieldValue`
216+
// will be identical to `substitute` – so no explicit mapping is needed.
217+
// If they are not equal, then we to write out the substitution.
219218
if (subFieldValue != substitute) {
220219
// TODO: the type could be imported from a library with a prefix!
221-
return "${expression} == null ? null : "
222-
"new List.generate(${expression}.length, "
223-
"(int $indexVal) => $subFieldValue)";
220+
expression = "${expression}?.map(($substitute) => $subFieldValue)";
221+
222+
// expression now represents an Iterable (even if it started as a List
223+
// ...resetting `isList` to `false`.
224+
isList = false;
224225
}
225226
}
226227

228+
if (!isList && _coreIterableChecker.isAssignableFromType(fieldType)) {
229+
// Then we need to add `?.toList()
230+
expression += "?.toList()";
231+
}
232+
227233
return expression;
228234
}
229235

@@ -252,6 +258,13 @@ class JsonSerializableGenerator
252258
_getIterableGenericType(searchType as InterfaceType);
253259

254260
var itemVal = "v$depth";
261+
var itemSubVal =
262+
_writeAccessToVar(itemVal, iterableGenericType, depth: depth + 1);
263+
264+
// If `itemSubVal` is the same, then we don't need to do anything fancy
265+
if (itemVal == itemSubVal) {
266+
return varExpression;
267+
}
255268

256269
var output = "($varExpression as List)?.map(($itemVal) => "
257270
"${_writeAccessToVar(itemVal, iterableGenericType, depth: depth+1)}"
@@ -264,7 +277,7 @@ class JsonSerializableGenerator
264277
return output;
265278
}
266279

267-
if (!searchType.isDynamic) {
280+
if (!searchType.isDynamic && !searchType.isObject) {
268281
return "$varExpression as $searchType";
269282
}
270283

test/json_serializable_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ void main() {
9494
await _getClassForCodeString('ParentObjectWithDynamicChildren');
9595
var output = await _generator.generate(element, null);
9696

97-
expect(output, contains('(json[\'children\'] as List)?.map('));
97+
expect(output, contains('children = json[\'children\'];'));
9898
});
9999

100100
test('class with list of int is cast for strong mode', () async {

test/test_files/json_test_example.dart

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,26 @@ import 'package:source_gen/generators/json_serializable.dart';
1111

1212
part 'json_test_example.g.dart';
1313

14+
@JsonSerializable()
15+
class ListGenericTests extends Object with _$ListGenericTestsSerializerMixin {
16+
ListGenericTests();
17+
18+
factory ListGenericTests.fromJson(Map<String, Object> json) =>
19+
_$ListGenericTestsFromJson(json);
20+
21+
Iterable iterable;
22+
Iterable<dynamic> dynamicIterable;
23+
Iterable<Object> objectIterable;
24+
Iterable<int> intIterable;
25+
Iterable<DateTime> dateTimeIterable;
26+
27+
List list;
28+
List<dynamic> dynamicList;
29+
List<Object> objectList;
30+
List<int> intList;
31+
List<DateTime> dateTimeList;
32+
}
33+
1434
@JsonSerializable()
1535
class Person extends Object with _$PersonSerializerMixin {
1636
final String firstName, middleName, lastName;

test/test_files/json_test_example.g.dart

Lines changed: 51 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)