diff --git a/json_serializable/lib/src/decode_helper.dart b/json_serializable/lib/src/decode_helper.dart index d2c199730..3a2ce5991 100644 --- a/json_serializable/lib/src/decode_helper.dart +++ b/json_serializable/lib/src/decode_helper.dart @@ -4,7 +4,6 @@ import 'package:analyzer/dart/element/element.dart'; import 'package:json_annotation/json_annotation.dart'; -import 'package:source_gen/source_gen.dart'; import 'helper_core.dart'; import 'json_literal_generator.dart'; @@ -247,8 +246,8 @@ _ConstructorData _writeConstructorInvocation( usedCtorParamsAndFields.add(arg.name); } - _validateConstructorArguments( - classElement, constructorArguments.followedBy(namedConstructorArguments)); + warnUndefinedElements( + constructorArguments.followedBy(namedConstructorArguments)); // fields that aren't already set by the constructor and that aren't final var remainingFieldsForInvocationBody = @@ -291,17 +290,3 @@ class _ConstructorData { _ConstructorData( this.content, this.fieldsToSet, this.usedCtorParamsAndFields); } - -void _validateConstructorArguments( - ClassElement element, Iterable constructorArguments) { - var undefinedArgs = - constructorArguments.where((pe) => pe.type.isUndefined).toList(); - if (undefinedArgs.isNotEmpty) { - var description = undefinedArgs.map((fe) => '`${fe.name}`').join(', '); - - throw new InvalidGenerationSourceError( - 'At least one constructor argument has an invalid type: $description.', - todo: 'Check names and imports.', - element: element); - } -} diff --git a/json_serializable/lib/src/helper_core.dart b/json_serializable/lib/src/helper_core.dart index 7431564fe..b934d7627 100644 --- a/json_serializable/lib/src/helper_core.dart +++ b/json_serializable/lib/src/helper_core.dart @@ -3,7 +3,7 @@ // BSD-style license that can be found in the LICENSE file. import 'package:analyzer/dart/element/element.dart'; - +import 'package:build/build.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:source_gen/source_gen.dart'; @@ -46,13 +46,24 @@ abstract class HelperCore { InvalidGenerationSourceError createInvalidGenerationError( String targetMember, FieldElement field, UnsupportedTypeError e) { - var extra = (field.type != e.type) ? ' because of type `${e.type}`' : ''; + var message = 'Could not generate `$targetMember` code for `${field.name}`'; + + var todo = 'Make sure all of the types are serializable.'; - var message = 'Could not generate `$targetMember` code for ' - '`${field.name}`$extra.\n${e.reason}'; + if (e.type.isUndefined) { + message = '$message because the type is undefined.'; + todo = "Check your imports. If you're trying to generate code for a " + 'Platform-provided type, you may have to specify a custom ' + '`$targetMember` in the associated `@JsonKey` annotation.'; + } else { + if (field.type != e.type) { + message = '$message because of type `${e.type}`'; + } + + message = '$message.\n${e.reason}'; + } - return new InvalidGenerationSourceError(message, - todo: 'Make sure all of the types are serializable.', element: field); + return new InvalidGenerationSourceError(message, todo: todo, element: field); } /// Returns a [String] representing the type arguments that exist on @@ -86,3 +97,13 @@ String genericClassArguments(ClassElement element, bool withConstraints) { .join(', '); return '<$values>'; } + +void warnUndefinedElements(Iterable elements) { + for (var element in elements.where((fe) => fe.type.isUndefined)) { + var span = spanForElement(element); + log.warning(''' +This element has an undefined type. It may causes issues when generated code. +${span.start.toolString} +${span.highlight()}'''); + } +} diff --git a/json_serializable/lib/src/json_serializable_generator.dart b/json_serializable/lib/src/json_serializable_generator.dart index c3e9a5073..fd19db991 100644 --- a/json_serializable/lib/src/json_serializable_generator.dart +++ b/json_serializable/lib/src/json_serializable_generator.dart @@ -247,15 +247,7 @@ Set _createSortedFieldSet(ClassElement element) { } } - var undefinedFields = fieldsList.where((fe) => fe.type.isUndefined).toList(); - if (undefinedFields.isNotEmpty) { - var description = undefinedFields.map((fe) => '`${fe.name}`').join(', '); - - throw new InvalidGenerationSourceError( - 'At least one field has an invalid type: $description.', - todo: 'Check names and imports.', - element: undefinedFields.first); - } + warnUndefinedElements(fieldsList); // Sort these in the order in which they appear in the class // Sadly, `classElement.fields` puts properties after fields diff --git a/json_serializable/lib/src/type_helpers/value_helper.dart b/json_serializable/lib/src/type_helpers/value_helper.dart index 4a145fc13..67f263187 100644 --- a/json_serializable/lib/src/type_helpers/value_helper.dart +++ b/json_serializable/lib/src/type_helpers/value_helper.dart @@ -14,6 +14,9 @@ class ValueHelper extends TypeHelper { @override String serialize( DartType targetType, String expression, SerializeContext context) { + if (targetType.isUndefined) { + return null; + } if (targetType.isDynamic || targetType.isObject || simpleJsonTypeChecker.isAssignableFromType(targetType)) { @@ -26,6 +29,9 @@ class ValueHelper extends TypeHelper { @override String deserialize( DartType targetType, String expression, DeserializeContext context) { + if (targetType.isUndefined) { + return null; + } if (targetType.isDynamic || targetType.isObject) { // just return it as-is. We'll hope it's safe. return expression; diff --git a/json_serializable/test/json_serializable_test.dart b/json_serializable/test/json_serializable_test.dart index 332908b0e..3434fc7b1 100644 --- a/json_serializable/test/json_serializable_test.dart +++ b/json_serializable/test/json_serializable_test.dart @@ -168,19 +168,44 @@ class _$TrivialNestedNonNullableJsonMapWrapper extends $JsonMapWrapper { 'Remove the JsonSerializable annotation from `annotatedMethod`.'); }); }); + group('unknown types', () { - test('in constructor arguments', () { - expectThrows( - 'UnknownCtorParamType', - 'At least one constructor argument has an invalid type: `number`.', - 'Check names and imports.'); + String flavorMessage(String flavor) => + 'Could not generate `$flavor` code for `number` ' + 'because the type is undefined.'; + + String flavorTodo(String flavor) => + 'Check your imports. If you\'re trying to generate code for a ' + 'Platform-provided type, you may have to specify a custom `$flavor` ' + 'in the associated `@JsonKey` annotation.'; + + group('fromJson', () { + var msg = flavorMessage('fromJson'); + var todo = flavorTodo('fromJson'); + test('in constructor arguments', () { + expectThrows('UnknownCtorParamType', msg, todo); + }); + + test('in fields', () { + expectThrows('UnknownFieldType', msg, todo); + }); }); - test('in fields', () { - expectThrows( - 'UnknownFieldType', - 'At least one field has an invalid type: `number`.', - 'Check names and imports.'); + group('toJson', () { + test('in fields', () { + expectThrows('UnknownFieldTypeToJsonOnly', flavorMessage('toJson'), + flavorTodo('toJson')); + }); + }); + + test('with proper convert methods', () { + var output = runForElementNamed('UnknownFieldTypeWithConvert'); + expect(output, contains("_everythingIs42(json['number'])")); + if (generator.useWrappers) { + expect(output, contains('_everythingIs42(_v.number)')); + } else { + expect(output, contains('_everythingIs42(number)')); + } }); }); diff --git a/json_serializable/test/src/json_serializable_test_input.dart b/json_serializable/test/src/json_serializable_test_input.dart index af7f086f9..f13a9690e 100644 --- a/json_serializable/test/src/json_serializable_test_input.dart +++ b/json_serializable/test/src/json_serializable_test_input.dart @@ -104,6 +104,21 @@ class UnknownFieldType { Bob number; } +@JsonSerializable(createFactory: false) +class UnknownFieldTypeToJsonOnly { + // ignore: undefined_class + Bob number; +} + +@JsonSerializable() +class UnknownFieldTypeWithConvert { + @JsonKey(fromJson: _everythingIs42, toJson: _everythingIs42) + // ignore: undefined_class + Bob number; +} + +dynamic _everythingIs42(Object input) => 42; + @JsonSerializable(createFactory: false) class NoSerializeFieldType { Stopwatch watch;