From 2ff08f85446a95660cb638feebfda1ce1b908518 Mon Sep 17 00:00:00 2001 From: phuongvt Date: Sat, 18 Nov 2023 03:09:00 +0700 Subject: [PATCH 1/2] Update json_converter_helper.dart [JsonConverter]: Support generators with constructor arguments Ex: DateTimeConverter(pattern: 'YYYY/MM/dd') --- .../type_helpers/json_converter_helper.dart | 207 +++++++++++------- 1 file changed, 128 insertions(+), 79 deletions(-) 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 ce78f19d1..f046fa447 100644 --- a/json_serializable/lib/src/type_helpers/json_converter_helper.dart +++ b/json_serializable/lib/src/type_helpers/json_converter_helper.dart @@ -21,10 +21,10 @@ class JsonConverterHelper extends TypeHelper { @override Object? serialize( - DartType targetType, - String expression, - TypeHelperContextWithConfig context, - ) { + DartType targetType, + String expression, + TypeHelperContextWithConfig context, + ) { final converter = _typeConverter(targetType, context); if (converter == null) { @@ -54,11 +54,11 @@ Json? $converterToJsonName( @override Object? deserialize( - DartType targetType, - String expression, - TypeHelperContextWithConfig context, - bool defaultProvided, - ) { + DartType targetType, + String expression, + TypeHelperContextWithConfig context, + bool defaultProvided, + ) { final converter = _typeConverter(targetType, context); if (converter == null) { return null; @@ -91,16 +91,14 @@ Value? $converterFromJsonName( } String _nullableJsonConverterLambdaResult( - _JsonConvertData converter, { - required String name, - required DartType targetType, - required String expression, - required String callback, -}) { + _JsonConvertData converter, { + required String name, + required DartType targetType, + required String expression, + required String callback, + }) { final jsonDisplayString = typeToCode(converter.jsonType); - final fieldTypeDisplayString = converter.isGeneric - ? typeToCode(targetType) - : typeToCode(converter.fieldType); + final fieldTypeDisplayString = converter.isGeneric ? typeToCode(targetType) : typeToCode(converter.fieldType); return '$name<$jsonDisplayString, $fieldTypeDisplayString>(' '$expression, $callback)'; @@ -113,31 +111,31 @@ class _JsonConvertData { final bool isGeneric; _JsonConvertData.className( - String className, - String accessor, - this.jsonType, - this.fieldType, - ) : accessString = 'const $className${_withAccessor(accessor)}()', + String className, + String? arguments, + String accessor, + this.jsonType, + this.fieldType, + ) : accessString = 'const $className${_withAccessor(accessor)}(${arguments ?? ''})', isGeneric = false; _JsonConvertData.genericClass( - String className, - String genericTypeArg, - String accessor, - this.jsonType, - this.fieldType, - ) : accessString = - '$className<$genericTypeArg>${_withAccessor(accessor)}()', + String className, + String? arguments, + String genericTypeArg, + String accessor, + this.jsonType, + this.fieldType, + ) : accessString = '$className<$genericTypeArg>${_withAccessor(accessor)}(${arguments ?? ''})', isGeneric = true; _JsonConvertData.propertyAccess( - this.accessString, - this.jsonType, - this.fieldType, - ) : isGeneric = false; + this.accessString, + this.jsonType, + this.fieldType, + ) : isGeneric = false; - static String _withAccessor(String accessor) => - accessor.isEmpty ? '' : '.$accessor'; + static String _withAccessor(String accessor) => accessor.isEmpty ? '' : '.$accessor'; } /// If there is no converter for the params, return `null`. @@ -146,9 +144,9 @@ class _JsonConvertData { /// /// Used to make sure we create a smart encoding function. bool? hasConverterNullEncode( - DartType targetType, - TypeHelperContextWithConfig ctx, -) { + DartType targetType, + TypeHelperContextWithConfig ctx, + ) { final data = _typeConverter(targetType, ctx); if (data == null) { @@ -159,34 +157,30 @@ bool? hasConverterNullEncode( } _JsonConvertData? _typeConverter( - DartType targetType, - TypeHelperContextWithConfig ctx, -) { + DartType targetType, + TypeHelperContextWithConfig ctx, + ) { List<_ConverterMatch> converterMatches(List items) => items .map( (annotation) => _compatibleMatch( - targetType, - annotation, - annotation.computeConstantValue()!, - ), - ) + targetType, + annotation, + annotation.computeConstantValue()!, + ), + ) .whereType<_ConverterMatch>() .toList(); var matchingAnnotations = converterMatches(ctx.fieldElement.metadata); if (matchingAnnotations.isEmpty) { - matchingAnnotations = - converterMatches(ctx.fieldElement.getter?.metadata ?? []); + matchingAnnotations = converterMatches(ctx.fieldElement.getter?.metadata ?? []); if (matchingAnnotations.isEmpty) { matchingAnnotations = converterMatches(ctx.classElement.metadata); if (matchingAnnotations.isEmpty) { - matchingAnnotations = ctx.config.converters - .map((e) => _compatibleMatch(targetType, null, e)) - .whereType<_ConverterMatch>() - .toList(); + matchingAnnotations = ctx.config.converters.map((e) => _compatibleMatch(targetType, null, e)).whereType<_ConverterMatch>().toList(); } } } @@ -195,9 +189,9 @@ _JsonConvertData? _typeConverter( } _JsonConvertData? _typeConverterFrom( - List<_ConverterMatch> matchingAnnotations, - DartType targetType, -) { + List<_ConverterMatch> matchingAnnotations, + DartType targetType, + ) { if (matchingAnnotations.isEmpty) { return null; } @@ -230,18 +224,13 @@ _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, - ); - } + // Support generators with constructor arguments + String? arguments = _argsFromRevivable(reviver); if (match.genericTypeArg != null) { return _JsonConvertData.genericClass( match.annotation.type!.element!.name!, + arguments, match.genericTypeArg!, reviver.accessor, match.jsonType, @@ -251,12 +240,73 @@ _JsonConvertData? _typeConverterFrom( return _JsonConvertData.className( match.annotation.type!.element!.name!, + arguments, reviver.accessor, match.jsonType, match.fieldType, ); } +String? _argsFromRevivable(Revivable reviver) { + String? arguments, positionalArguments, namedArguments; + if (reviver.positionalArguments.isNotEmpty) { + positionalArguments = reviver.positionalArguments.map((DartObject arg) => _argumentValueFromDartObject(arg)).join(', '); + } + if (reviver.namedArguments.isNotEmpty) { + namedArguments = reviver.namedArguments.keys + .map((String key) { + dynamic arg = _argumentValueFromDartObject(reviver.namedArguments[key]); + if (null != arg) return '$key: $arg'; + return null; + }) + .where(_isNotEmpty) + .join(', '); + } + arguments = [positionalArguments, namedArguments].where(_isNotEmpty).join(', '); + return arguments; +} + +bool _isNotEmpty(dynamic element) => null != element; + +dynamic _argumentValueFromDartObject(DartObject? obj) { + try { + if (null != obj) { + if (obj.type?.isDartCoreString == true) { + return '\'${obj.toStringValue()}\''; + } else if (obj.type?.isDartCoreInt == true) { + return obj.toIntValue(); + } else if (obj.type?.isDartCoreDouble == true) { + return obj.toDoubleValue(); + } else if (obj.type?.isDartCoreBool == true) { + return obj.toBoolValue(); + } else if (obj.type?.isDartCoreIterable == true || obj.type?.isDartCoreList == true) { + return obj.toListValue(); + } else if (obj.type?.isDartCoreMap == true) { + return obj.toMapValue(); + } else if (obj.type?.isDartCoreSet == true) { + return obj.toSetValue(); + } else if (obj.type?.isDartCoreSymbol == true) { + return obj.toSymbolValue(); + } else { + ExecutableElement? executable = obj.toFunctionValue(); + if (null != executable) { + return executable.displayName; + } else if (null != obj.type) { + String? typeDisplayString = obj.type?.getDisplayString(withNullability: false); + if (null != typeDisplayString && typeDisplayString != 'Null') { + final reviver = ConstantReader(obj).revive(); + String? arguments = _argsFromRevivable(reviver); + return '$typeDisplayString(${arguments ?? ''})'; + } + } + } + } + } catch (e) { + print(e); + } + return null; +} + class _ConverterMatch { final DartObject annotation; final DartType fieldType; @@ -265,24 +315,23 @@ class _ConverterMatch { final String? genericTypeArg; _ConverterMatch( - this.elementAnnotation, - this.annotation, - this.jsonType, - this.genericTypeArg, - this.fieldType, - ); + this.elementAnnotation, + this.annotation, + this.jsonType, + this.genericTypeArg, + this.fieldType, + ); } _ConverterMatch? _compatibleMatch( - DartType targetType, - ElementAnnotation? annotation, - DartObject constantValue, -) { + DartType targetType, + ElementAnnotation? annotation, + DartObject constantValue, + ) { final converterClassElement = constantValue.type!.element as ClassElement; - final jsonConverterSuper = - converterClassElement.allSupertypes.singleWhereOrNull( - (e) => _jsonConverterChecker.isExactly(e.element), + final jsonConverterSuper = converterClassElement.allSupertypes.singleWhereOrNull( + (e) => _jsonConverterChecker.isExactly(e.element), ); if (jsonConverterSuper == null) { @@ -311,8 +360,8 @@ _ConverterMatch? _compatibleMatch( if (converterClassElement.typeParameters.length > 1) { throw InvalidGenerationSourceError( '`JsonConverter` implementations can have no more than one type ' - 'argument. `${converterClassElement.name}` has ' - '${converterClassElement.typeParameters.length}.', + 'argument. `${converterClassElement.name}` has ' + '${converterClassElement.typeParameters.length}.', element: converterClassElement); } From 36df84ad7531e81ba96c352328412c815feebcc2 Mon Sep 17 00:00:00 2001 From: Phuong Vu Date: Sat, 18 Nov 2023 10:00:32 +0700 Subject: [PATCH 2/2] Update json_converter_helper.dart --- .../type_helpers/json_converter_helper.dart | 137 ++++++++++-------- 1 file changed, 73 insertions(+), 64 deletions(-) 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 f046fa447..0ca647656 100644 --- a/json_serializable/lib/src/type_helpers/json_converter_helper.dart +++ b/json_serializable/lib/src/type_helpers/json_converter_helper.dart @@ -21,10 +21,10 @@ class JsonConverterHelper extends TypeHelper { @override Object? serialize( - DartType targetType, - String expression, - TypeHelperContextWithConfig context, - ) { + DartType targetType, + String expression, + TypeHelperContextWithConfig context, + ) { final converter = _typeConverter(targetType, context); if (converter == null) { @@ -54,11 +54,11 @@ Json? $converterToJsonName( @override Object? deserialize( - DartType targetType, - String expression, - TypeHelperContextWithConfig context, - bool defaultProvided, - ) { + DartType targetType, + String expression, + TypeHelperContextWithConfig context, + bool defaultProvided, + ) { final converter = _typeConverter(targetType, context); if (converter == null) { return null; @@ -91,14 +91,16 @@ Value? $converterFromJsonName( } String _nullableJsonConverterLambdaResult( - _JsonConvertData converter, { - required String name, - required DartType targetType, - required String expression, - required String callback, - }) { + _JsonConvertData converter, { + required String name, + required DartType targetType, + required String expression, + required String callback, +}) { final jsonDisplayString = typeToCode(converter.jsonType); - final fieldTypeDisplayString = converter.isGeneric ? typeToCode(targetType) : typeToCode(converter.fieldType); + final fieldTypeDisplayString = converter.isGeneric + ? typeToCode(targetType) + : typeToCode(converter.fieldType); return '$name<$jsonDisplayString, $fieldTypeDisplayString>(' '$expression, $callback)'; @@ -111,31 +113,33 @@ class _JsonConvertData { final bool isGeneric; _JsonConvertData.className( - String className, - String? arguments, - String accessor, - this.jsonType, - this.fieldType, - ) : accessString = 'const $className${_withAccessor(accessor)}(${arguments ?? ''})', + String className, + String? arguments, + String accessor, + this.jsonType, + this.fieldType, + ) : accessString = 'const $className${_withAccessor(accessor)}(${arguments ?? ''})', isGeneric = false; _JsonConvertData.genericClass( - String className, - String? arguments, - String genericTypeArg, - String accessor, - this.jsonType, - this.fieldType, - ) : accessString = '$className<$genericTypeArg>${_withAccessor(accessor)}(${arguments ?? ''})', + String className, + String? arguments, + String genericTypeArg, + String accessor, + this.jsonType, + this.fieldType, + ) : accessString = + '$className<$genericTypeArg>${_withAccessor(accessor)}(${arguments ?? ''})', isGeneric = true; _JsonConvertData.propertyAccess( - this.accessString, - this.jsonType, - this.fieldType, - ) : isGeneric = false; + this.accessString, + this.jsonType, + this.fieldType, + ) : isGeneric = false; - static String _withAccessor(String accessor) => accessor.isEmpty ? '' : '.$accessor'; + static String _withAccessor(String accessor) => + accessor.isEmpty ? '' : '.$accessor'; } /// If there is no converter for the params, return `null`. @@ -144,9 +148,9 @@ class _JsonConvertData { /// /// Used to make sure we create a smart encoding function. bool? hasConverterNullEncode( - DartType targetType, - TypeHelperContextWithConfig ctx, - ) { + DartType targetType, + TypeHelperContextWithConfig ctx, +) { final data = _typeConverter(targetType, ctx); if (data == null) { @@ -157,30 +161,34 @@ bool? hasConverterNullEncode( } _JsonConvertData? _typeConverter( - DartType targetType, - TypeHelperContextWithConfig ctx, - ) { + DartType targetType, + TypeHelperContextWithConfig ctx, +) { List<_ConverterMatch> converterMatches(List items) => items .map( (annotation) => _compatibleMatch( - targetType, - annotation, - annotation.computeConstantValue()!, - ), - ) + targetType, + annotation, + annotation.computeConstantValue()!, + ), + ) .whereType<_ConverterMatch>() .toList(); var matchingAnnotations = converterMatches(ctx.fieldElement.metadata); if (matchingAnnotations.isEmpty) { - matchingAnnotations = converterMatches(ctx.fieldElement.getter?.metadata ?? []); + matchingAnnotations = + converterMatches(ctx.fieldElement.getter?.metadata ?? []); if (matchingAnnotations.isEmpty) { matchingAnnotations = converterMatches(ctx.classElement.metadata); if (matchingAnnotations.isEmpty) { - matchingAnnotations = ctx.config.converters.map((e) => _compatibleMatch(targetType, null, e)).whereType<_ConverterMatch>().toList(); + matchingAnnotations = ctx.config.converters + .map((e) => _compatibleMatch(targetType, null, e)) + .whereType<_ConverterMatch>() + .toList(); } } } @@ -189,9 +197,9 @@ _JsonConvertData? _typeConverter( } _JsonConvertData? _typeConverterFrom( - List<_ConverterMatch> matchingAnnotations, - DartType targetType, - ) { + List<_ConverterMatch> matchingAnnotations, + DartType targetType, +) { if (matchingAnnotations.isEmpty) { return null; } @@ -315,23 +323,24 @@ class _ConverterMatch { final String? genericTypeArg; _ConverterMatch( - this.elementAnnotation, - this.annotation, - this.jsonType, - this.genericTypeArg, - this.fieldType, - ); + this.elementAnnotation, + this.annotation, + this.jsonType, + this.genericTypeArg, + this.fieldType, + ); } _ConverterMatch? _compatibleMatch( - DartType targetType, - ElementAnnotation? annotation, - DartObject constantValue, - ) { + DartType targetType, + ElementAnnotation? annotation, + DartObject constantValue, +) { final converterClassElement = constantValue.type!.element as ClassElement; - final jsonConverterSuper = converterClassElement.allSupertypes.singleWhereOrNull( - (e) => _jsonConverterChecker.isExactly(e.element), + final jsonConverterSuper = + converterClassElement.allSupertypes.singleWhereOrNull( + (e) => _jsonConverterChecker.isExactly(e.element), ); if (jsonConverterSuper == null) { @@ -360,8 +369,8 @@ _ConverterMatch? _compatibleMatch( if (converterClassElement.typeParameters.length > 1) { throw InvalidGenerationSourceError( '`JsonConverter` implementations can have no more than one type ' - 'argument. `${converterClassElement.name}` has ' - '${converterClassElement.typeParameters.length}.', + 'argument. `${converterClassElement.name}` has ' + '${converterClassElement.typeParameters.length}.', element: converterClassElement); }