Skip to content

Commit 6befa48

Browse files
authored
feature: allow undefined types as long as they have a converter (#243)
Types from Platform-specific libraries (like dart:ui in Flutter) don't resolve properly during code generation. See dart-lang/build#733 This update allows undefined types to be handled with custom to/fromJson for an associated field Fixes #236
1 parent 503c164 commit 6befa48

File tree

6 files changed

+86
-42
lines changed

6 files changed

+86
-42
lines changed

json_serializable/lib/src/decode_helper.dart

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
import 'package:analyzer/dart/element/element.dart';
66
import 'package:json_annotation/json_annotation.dart';
7-
import 'package:source_gen/source_gen.dart';
87

98
import 'helper_core.dart';
109
import 'json_literal_generator.dart';
@@ -247,8 +246,8 @@ _ConstructorData _writeConstructorInvocation(
247246
usedCtorParamsAndFields.add(arg.name);
248247
}
249248

250-
_validateConstructorArguments(
251-
classElement, constructorArguments.followedBy(namedConstructorArguments));
249+
warnUndefinedElements(
250+
constructorArguments.followedBy(namedConstructorArguments));
252251

253252
// fields that aren't already set by the constructor and that aren't final
254253
var remainingFieldsForInvocationBody =
@@ -291,17 +290,3 @@ class _ConstructorData {
291290
_ConstructorData(
292291
this.content, this.fieldsToSet, this.usedCtorParamsAndFields);
293292
}
294-
295-
void _validateConstructorArguments(
296-
ClassElement element, Iterable<ParameterElement> constructorArguments) {
297-
var undefinedArgs =
298-
constructorArguments.where((pe) => pe.type.isUndefined).toList();
299-
if (undefinedArgs.isNotEmpty) {
300-
var description = undefinedArgs.map((fe) => '`${fe.name}`').join(', ');
301-
302-
throw new InvalidGenerationSourceError(
303-
'At least one constructor argument has an invalid type: $description.',
304-
todo: 'Check names and imports.',
305-
element: element);
306-
}
307-
}

json_serializable/lib/src/helper_core.dart

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// BSD-style license that can be found in the LICENSE file.
44

55
import 'package:analyzer/dart/element/element.dart';
6-
6+
import 'package:build/build.dart';
77
import 'package:json_annotation/json_annotation.dart';
88
import 'package:source_gen/source_gen.dart';
99

@@ -46,13 +46,24 @@ abstract class HelperCore {
4646

4747
InvalidGenerationSourceError createInvalidGenerationError(
4848
String targetMember, FieldElement field, UnsupportedTypeError e) {
49-
var extra = (field.type != e.type) ? ' because of type `${e.type}`' : '';
49+
var message = 'Could not generate `$targetMember` code for `${field.name}`';
50+
51+
var todo = 'Make sure all of the types are serializable.';
5052

51-
var message = 'Could not generate `$targetMember` code for '
52-
'`${field.name}`$extra.\n${e.reason}';
53+
if (e.type.isUndefined) {
54+
message = '$message because the type is undefined.';
55+
todo = "Check your imports. If you're trying to generate code for a "
56+
'Platform-provided type, you may have to specify a custom '
57+
'`$targetMember` in the associated `@JsonKey` annotation.';
58+
} else {
59+
if (field.type != e.type) {
60+
message = '$message because of type `${e.type}`';
61+
}
62+
63+
message = '$message.\n${e.reason}';
64+
}
5365

54-
return new InvalidGenerationSourceError(message,
55-
todo: 'Make sure all of the types are serializable.', element: field);
66+
return new InvalidGenerationSourceError(message, todo: todo, element: field);
5667
}
5768

5869
/// Returns a [String] representing the type arguments that exist on
@@ -86,3 +97,13 @@ String genericClassArguments(ClassElement element, bool withConstraints) {
8697
.join(', ');
8798
return '<$values>';
8899
}
100+
101+
void warnUndefinedElements(Iterable<VariableElement> elements) {
102+
for (var element in elements.where((fe) => fe.type.isUndefined)) {
103+
var span = spanForElement(element);
104+
log.warning('''
105+
This element has an undefined type. It may causes issues when generated code.
106+
${span.start.toolString}
107+
${span.highlight()}''');
108+
}
109+
}

json_serializable/lib/src/json_serializable_generator.dart

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -247,15 +247,7 @@ Set<FieldElement> _createSortedFieldSet(ClassElement element) {
247247
}
248248
}
249249

250-
var undefinedFields = fieldsList.where((fe) => fe.type.isUndefined).toList();
251-
if (undefinedFields.isNotEmpty) {
252-
var description = undefinedFields.map((fe) => '`${fe.name}`').join(', ');
253-
254-
throw new InvalidGenerationSourceError(
255-
'At least one field has an invalid type: $description.',
256-
todo: 'Check names and imports.',
257-
element: undefinedFields.first);
258-
}
250+
warnUndefinedElements(fieldsList);
259251

260252
// Sort these in the order in which they appear in the class
261253
// Sadly, `classElement.fields` puts properties after fields

json_serializable/lib/src/type_helpers/value_helper.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ class ValueHelper extends TypeHelper {
1414
@override
1515
String serialize(
1616
DartType targetType, String expression, SerializeContext context) {
17+
if (targetType.isUndefined) {
18+
return null;
19+
}
1720
if (targetType.isDynamic ||
1821
targetType.isObject ||
1922
simpleJsonTypeChecker.isAssignableFromType(targetType)) {
@@ -26,6 +29,9 @@ class ValueHelper extends TypeHelper {
2629
@override
2730
String deserialize(
2831
DartType targetType, String expression, DeserializeContext context) {
32+
if (targetType.isUndefined) {
33+
return null;
34+
}
2935
if (targetType.isDynamic || targetType.isObject) {
3036
// just return it as-is. We'll hope it's safe.
3137
return expression;

json_serializable/test/json_serializable_test.dart

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -168,19 +168,44 @@ class _$TrivialNestedNonNullableJsonMapWrapper extends $JsonMapWrapper {
168168
'Remove the JsonSerializable annotation from `annotatedMethod`.');
169169
});
170170
});
171+
171172
group('unknown types', () {
172-
test('in constructor arguments', () {
173-
expectThrows(
174-
'UnknownCtorParamType',
175-
'At least one constructor argument has an invalid type: `number`.',
176-
'Check names and imports.');
173+
String flavorMessage(String flavor) =>
174+
'Could not generate `$flavor` code for `number` '
175+
'because the type is undefined.';
176+
177+
String flavorTodo(String flavor) =>
178+
'Check your imports. If you\'re trying to generate code for a '
179+
'Platform-provided type, you may have to specify a custom `$flavor` '
180+
'in the associated `@JsonKey` annotation.';
181+
182+
group('fromJson', () {
183+
var msg = flavorMessage('fromJson');
184+
var todo = flavorTodo('fromJson');
185+
test('in constructor arguments', () {
186+
expectThrows('UnknownCtorParamType', msg, todo);
187+
});
188+
189+
test('in fields', () {
190+
expectThrows('UnknownFieldType', msg, todo);
191+
});
177192
});
178193

179-
test('in fields', () {
180-
expectThrows(
181-
'UnknownFieldType',
182-
'At least one field has an invalid type: `number`.',
183-
'Check names and imports.');
194+
group('toJson', () {
195+
test('in fields', () {
196+
expectThrows('UnknownFieldTypeToJsonOnly', flavorMessage('toJson'),
197+
flavorTodo('toJson'));
198+
});
199+
});
200+
201+
test('with proper convert methods', () {
202+
var output = runForElementNamed('UnknownFieldTypeWithConvert');
203+
expect(output, contains("_everythingIs42(json['number'])"));
204+
if (generator.useWrappers) {
205+
expect(output, contains('_everythingIs42(_v.number)'));
206+
} else {
207+
expect(output, contains('_everythingIs42(number)'));
208+
}
184209
});
185210
});
186211

json_serializable/test/src/json_serializable_test_input.dart

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,21 @@ class UnknownFieldType {
104104
Bob number;
105105
}
106106

107+
@JsonSerializable(createFactory: false)
108+
class UnknownFieldTypeToJsonOnly {
109+
// ignore: undefined_class
110+
Bob number;
111+
}
112+
113+
@JsonSerializable()
114+
class UnknownFieldTypeWithConvert {
115+
@JsonKey(fromJson: _everythingIs42, toJson: _everythingIs42)
116+
// ignore: undefined_class
117+
Bob number;
118+
}
119+
120+
dynamic _everythingIs42(Object input) => 42;
121+
107122
@JsonSerializable(createFactory: false)
108123
class NoSerializeFieldType {
109124
Stopwatch watch;

0 commit comments

Comments
 (0)