diff --git a/json_serializable/lib/src/type_helpers/json_converter_helper.dart b/json_serializable/lib/src/type_helpers/json_converter_helper.dart index 1288b7c9..ad060e9f 100644 --- a/json_serializable/lib/src/type_helpers/json_converter_helper.dart +++ b/json_serializable/lib/src/type_helpers/json_converter_helper.dart @@ -9,6 +9,7 @@ import 'package:json_annotation/json_annotation.dart'; import 'package:source_gen/source_gen.dart'; import 'package:source_helper/source_helper.dart'; +import '../json_literal_generator.dart'; import '../lambda_result.dart'; import '../type_helper.dart'; import '../utils.dart'; @@ -115,18 +116,22 @@ class _JsonConvertData { String className, String accessor, this.jsonType, - this.fieldType, - ) : accessString = 'const $className${_withAccessor(accessor)}()', - isGeneric = false; + this.fieldType, [ + String constructorArgs = '', + ]) : accessString = + 'const $className${_withAccessor(accessor)}($constructorArgs)', + isGeneric = false; _JsonConvertData.genericClass( String className, String genericTypeArg, String accessor, this.jsonType, - this.fieldType, - ) : accessString = '$className<$genericTypeArg>${_withAccessor(accessor)}()', - isGeneric = true; + this.fieldType, [ + String constructorArgs = '', + ]) : accessString = + '$className<$genericTypeArg>${_withAccessor(accessor)}($constructorArgs)', + isGeneric = true; _JsonConvertData.propertyAccess( this.accessString, @@ -234,13 +239,7 @@ _JsonConvertData? _typeConverterFrom( final reviver = ConstantReader(match.annotation).revive(); - if (reviver.namedArguments.isNotEmpty || - reviver.positionalArguments.isNotEmpty) { - throw InvalidGenerationSourceError( - 'Generators with constructor arguments are not supported.', - element: match.elementAnnotation?.element, - ); - } + final args = _parseArguments(reviver); if (match.genericTypeArg != null) { return _JsonConvertData.genericClass( @@ -249,6 +248,7 @@ _JsonConvertData? _typeConverterFrom( reviver.accessor, match.jsonType, match.fieldType, + args, ); } @@ -257,6 +257,61 @@ _JsonConvertData? _typeConverterFrom( reviver.accessor, match.jsonType, match.fieldType, + args, + ); +} + +String _parseArguments(Revivable reviver) { + final args = []; + for (final arg in reviver.positionalArguments) { + args.add(_dartObjectToString(arg)); + } + for (final entry in reviver.namedArguments.entries) { + args.add('${entry.key}: ${_dartObjectToString(entry.value)}'); + } + return args.join(', '); +} + +String _dartObjectToString(DartObject object) { + final reader = ConstantReader(object); + + if (reader.isNull) { + return 'null'; + } + + if (reader.isBool || reader.isInt || reader.isDouble || reader.isString) { + return jsonLiteralAsDart(reader.literalValue); + } + + if (reader.isList) { + final list = reader.listValue.map(_dartObjectToString).join(', '); + return '[$list]'; + } + + if (reader.isMap) { + final map = reader.mapValue.entries + .map((e) { + return '${_dartObjectToString(e.key!)}: ${_dartObjectToString(e.value!)}'; + }) + .join(', '); + return '{$map}'; + } + + if (reader.isSet) { + final set = reader.setValue.map(_dartObjectToString).join(', '); + return '{$set}'; + } + + final type = object.type; + if (type is InterfaceType && type.element is EnumElement) { + final enumElement = type.element as EnumElement; + final index = object.getField('index')!.toIntValue()!; + return '${type.element.name}.${enumElement.constants[index].name}'; + } + + throw InvalidGenerationSourceError( + 'Unsupported argument type for JsonConverter: ${object.type}', + element: object.type?.element, ); } diff --git a/json_serializable/test/json_serializable_test.dart b/json_serializable/test/json_serializable_test.dart index 0f58f71b..e97e83a8 100644 --- a/json_serializable/test/json_serializable_test.dart +++ b/json_serializable/test/json_serializable_test.dart @@ -94,8 +94,11 @@ const _expectedAnnotatedTests = { 'JsonConverterCtorParams', 'JsonConverterDuplicateAnnotations', 'JsonConverterNamedCtor', + 'JsonConverterListParams', + 'JsonConverterNamedParams', 'JsonConverterNullableToNonNullable', 'JsonConverterOnGetter', + 'JsonConverterStringParams', 'JsonConverterWithBadTypeArg', 'JsonValueValid', 'JsonValueWithBool', diff --git a/json_serializable/test/src/_json_serializable_test_input.dart b/json_serializable/test/src/_json_serializable_test_input.dart index 20cd8739..6ada7c88 100644 --- a/json_serializable/test/src/_json_serializable_test_input.dart +++ b/json_serializable/test/src/_json_serializable_test_input.dart @@ -18,6 +18,7 @@ part 'extends_jsonkey_override.dart'; part 'generic_test_input.dart'; part 'inheritance_test_input.dart'; part 'json_converter_test_input.dart'; +part 'json_converter_params_test_input.dart'; part 'map_key_variety_test_input.dart'; part 'setter_test_input.dart'; part 'to_from_json_test_input.dart'; diff --git a/json_serializable/test/src/json_converter_params_test_input.dart b/json_serializable/test/src/json_converter_params_test_input.dart new file mode 100644 index 00000000..e76d935d --- /dev/null +++ b/json_serializable/test/src/json_converter_params_test_input.dart @@ -0,0 +1,137 @@ +// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// @dart=3.8 + +// ignore_for_file: inference_failure_on_instance_creation + +part of '_json_serializable_test_input.dart'; + +@ShouldGenerate(r''' +JsonConverterCtorParams _$JsonConverterCtorParamsFromJson( + Map json, +) => JsonConverterCtorParams() + ..value = const _ConverterWithCtorParams( + 42, + ).fromJson((json['value'] as num).toInt()); + +Map _$JsonConverterCtorParamsToJson( + JsonConverterCtorParams instance, +) => { + 'value': const _ConverterWithCtorParams(42).toJson(instance.value), +}; +''') +@JsonSerializable() +@_ConverterWithCtorParams(42) +class JsonConverterCtorParams { + late Duration value; +} + +class _ConverterWithCtorParams implements JsonConverter { + final int param; + + const _ConverterWithCtorParams(this.param); + + @override + Duration fromJson(int json) => throw UnimplementedError(); + + @override + int toJson(Duration object) => 0; +} + +@ShouldGenerate(r''' +JsonConverterStringParams _$JsonConverterStringParamsFromJson( + Map json, +) => JsonConverterStringParams() + ..value = const _ConverterWithStringParams( + 'cool', + ).fromJson((json['value'] as num).toInt()); + +Map _$JsonConverterStringParamsToJson( + JsonConverterStringParams instance, +) => { + 'value': const _ConverterWithStringParams('cool').toJson(instance.value), +}; +''') +@JsonSerializable() +@_ConverterWithStringParams('cool') +class JsonConverterStringParams { + late Duration value; +} + +class _ConverterWithStringParams implements JsonConverter { + final String param; + + const _ConverterWithStringParams(this.param); + + @override + Duration fromJson(int json) => throw UnimplementedError(); + + @override + int toJson(Duration object) => 0; +} + +@ShouldGenerate(r''' +JsonConverterListParams _$JsonConverterListParamsFromJson( + Map json, +) => JsonConverterListParams() + ..value = const _ConverterWithListParams([ + 42, + ]).fromJson((json['value'] as num).toInt()); + +Map _$JsonConverterListParamsToJson( + JsonConverterListParams instance, +) => { + 'value': const _ConverterWithListParams([42]).toJson(instance.value), +}; +''') +@JsonSerializable() +@_ConverterWithListParams([42]) +class JsonConverterListParams { + late Duration value; +} + +class _ConverterWithListParams implements JsonConverter { + final List param; + + const _ConverterWithListParams(this.param); + + @override + Duration fromJson(int json) => throw UnimplementedError(); + + @override + int toJson(Duration object) => 0; +} + +@ShouldGenerate(r''' +JsonConverterNamedParams _$JsonConverterNamedParamsFromJson( + Map json, +) => JsonConverterNamedParams() + ..value = const _ConverterWithNamedParams( + param: 42, + ).fromJson((json['value'] as num).toInt()); + +Map _$JsonConverterNamedParamsToJson( + JsonConverterNamedParams instance, +) => { + 'value': const _ConverterWithNamedParams(param: 42).toJson(instance.value), +}; +''') +@JsonSerializable() +@_ConverterWithNamedParams(param: 42) +class JsonConverterNamedParams { + late Duration value; +} + +class _ConverterWithNamedParams implements JsonConverter { + final int param; + + const _ConverterWithNamedParams({required this.param}); + + @override + Duration fromJson(int json) => throw UnimplementedError(); + + @override + int toJson(Duration object) => 0; +} diff --git a/json_serializable/test/src/json_converter_test_input.dart b/json_serializable/test/src/json_converter_test_input.dart index 1c082dd4..c501e580 100644 --- a/json_serializable/test/src/json_converter_test_input.dart +++ b/json_serializable/test/src/json_converter_test_input.dart @@ -152,28 +152,6 @@ class _DurationMillisecondConverter implements JsonConverter { int toJson(Duration object) => throw UnimplementedError(); } -@ShouldThrow( - 'Generators with constructor arguments are not supported.', - element: 'new', -) -@JsonSerializable() -@_ConverterWithCtorParams(42) -class JsonConverterCtorParams { - late Duration value; -} - -class _ConverterWithCtorParams implements JsonConverter { - final int param; - - const _ConverterWithCtorParams(this.param); - - @override - Duration fromJson(int json) => throw UnimplementedError(); - - @override - int toJson(Duration object) => 0; -} - @ShouldGenerate(r''' Map _$JsonConverterOnGetterToJson( JsonConverterOnGetter instance,