From 78ea7429243f5b4c1eea085df2bff6aa0336c8d5 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sat, 21 Jan 2023 23:27:52 +0900 Subject: [PATCH 1/3] Commonize function to reconstruct Class from KmType --- .../jackson/module/kotlin/InternalCommons.kt | 4 ++++ .../KotlinNamesAnnotationIntrospector.kt | 21 +++++++------------ 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/InternalCommons.kt b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/InternalCommons.kt index 017b4080..ad1e9af6 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/InternalCommons.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/InternalCommons.kt @@ -2,6 +2,7 @@ package com.fasterxml.jackson.module.kotlin import kotlinx.metadata.Flag import kotlinx.metadata.KmClass +import kotlinx.metadata.KmClassifier import kotlinx.metadata.KmConstructor import kotlinx.metadata.KmProperty import kotlinx.metadata.KmType @@ -67,6 +68,9 @@ internal val defaultConstructorMarker: Class<*> by lazy { // but are ignored because they do not result in errors in internal use cases. internal fun String.reconstructClass(): Class<*> = Class.forName(this.replace(".", "$").replace("/", ".")) +internal fun KmType.reconstructClassOrNull(): Class<*>? = (classifier as? KmClassifier.Class) + ?.let { kotlin.runCatching { it.name.reconstructClass() }.getOrNull() } + internal fun KmClass.findKmConstructor(constructor: Constructor<*>): KmConstructor? { val descHead = constructor.parameterTypes.toDescString() val desc = descHead + "V" diff --git a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinNamesAnnotationIntrospector.kt b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinNamesAnnotationIntrospector.kt index c91ca60b..037a3e00 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinNamesAnnotationIntrospector.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinNamesAnnotationIntrospector.kt @@ -140,13 +140,11 @@ internal class KotlinNamesAnnotationIntrospector( } val kotlinProperty = cache.getKmClass(getter.declaringClass)?.findPropertyByGetter(getter) - return (kotlinProperty?.returnType?.classifier as? KmClassifier.Class)?.let { classifier -> - // Since there was no way to directly determine whether returnType is a value class or not, - // Class is restored and processed. - // If the cost of this process is significant, consider caching it. - runCatching { classifier.name.reconstructClass() } - .getOrNull() - ?.takeIf { takePredicate(it) } + // Since there was no way to directly determine whether returnType is a value class or not, + // Class is restored and processed. + // If the cost of this process is significant, consider caching it. + return kotlinProperty?.returnType?.reconstructClassOrNull()?.let { clazz -> + clazz.takeIf(takePredicate) ?.let { ValueClassBoxConverter(getter.returnType, it) } } } @@ -163,12 +161,9 @@ internal class KotlinNamesAnnotationIntrospector( } private fun ValueParameter.createValueClassUnboxConverterOrNull(rawType: Class<*>): ValueClassUnboxConverter<*>? { - return (this.type.classifier as? KmClassifier.Class)?.let { classifier -> - runCatching { classifier.name.reconstructClass() } - .getOrNull() - ?.takeIf { it.isUnboxableValueClass() && it != rawType } - ?.let { ValueClassUnboxConverter(it) } - } + return type.reconstructClassOrNull() + ?.takeIf { it.isUnboxableValueClass() && it != rawType } + ?.let { ValueClassUnboxConverter(it) } } // If the collection type argument cannot be obtained, treat it as nullable From 620872ee7432ad4a1f14209f258705affbfa0f95 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sun, 22 Jan 2023 14:28:47 +0900 Subject: [PATCH 2/3] Add KotlinClassIntrospector for inject synthetic annotations to constructor --- .../module/kotlin/KotlinClassIntrospector.kt | 57 +++++++++++++++++++ .../jackson/module/kotlin/KotlinModule.kt | 2 + 2 files changed, 59 insertions(+) create mode 100644 src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinClassIntrospector.kt diff --git a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinClassIntrospector.kt b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinClassIntrospector.kt new file mode 100644 index 00000000..1d634b50 --- /dev/null +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinClassIntrospector.kt @@ -0,0 +1,57 @@ +package com.fasterxml.jackson.module.kotlin + +import com.fasterxml.jackson.databind.JavaType +import com.fasterxml.jackson.databind.SerializationConfig +import com.fasterxml.jackson.databind.introspect.AnnotatedConstructor +import com.fasterxml.jackson.databind.introspect.BasicBeanDescription +import com.fasterxml.jackson.databind.introspect.BasicClassIntrospector +import com.fasterxml.jackson.databind.introspect.POJOPropertiesCollector + +// If the constructor has value class parameter, +// annotations given to synthetic constructor parameter is injected into that constructor parameter. +private fun AnnotatedConstructor.injectSyntheticAnnotations() { + val constructor = annotated + + @Suppress("UNCHECKED_CAST") + val syntheticParams = constructor.parameterTypes.copyOf(constructor.parameterCount + 1) + .apply { this[constructor.parameterCount] = defaultConstructorMarker } as Array> + // Try to get syntheticConstructor, and if not, do nothing. + val syntheticConstructor = runCatching { declaringClass.getDeclaredConstructor(*syntheticParams) } + .getOrNull() + ?: return + + (0 until constructor.parameterCount).forEach { i -> + val map = getParameterAnnotations(i) + syntheticConstructor.parameterAnnotations[i].forEach { map.add(it) } + } +} + +internal class KotlinBeanDescription(coll: POJOPropertiesCollector) : BasicBeanDescription(coll) { + init { + this.constructors?.forEach { it.injectSyntheticAnnotations() } + } +} + +// Almost all copies of super @2.14.1 +internal object KotlinClassIntrospector : BasicClassIntrospector() { + override fun forSerialization( + config: SerializationConfig, + type: JavaType, + r: MixInResolver + ): BasicBeanDescription { + // minor optimization: for some JDK types do minimal introspection + return _findStdTypeDesc(config, type) + // As per [databind#550], skip full introspection for some of standard + // structured types as well + ?: _findStdJdkCollectionDesc(config, type) + ?: run { + val coll = collectProperties(config, type, r, true) + + if (type.rawClass.annotations.any { it is Metadata }) { + KotlinBeanDescription(coll) + } else { + BasicBeanDescription.forDeserialization(coll) + } + } + } +} diff --git a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinModule.kt b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinModule.kt index 890cbc54..8129d940 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinModule.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinModule.kt @@ -77,6 +77,8 @@ public class KotlinModule private constructor( ) context.appendAnnotationIntrospector(KotlinNamesAnnotationIntrospector(this, strictNullChecks, cache)) + context.setClassIntrospector(KotlinClassIntrospector) + context.addDeserializers(KotlinDeserializers()) context.addKeyDeserializers(KotlinKeyDeserializers) context.addSerializers(KotlinSerializers()) From 767604363d29cb078c78b568515521463c6f1167 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sun, 22 Jan 2023 14:36:15 +0900 Subject: [PATCH 3/3] Fix test #46 fixed on serialize --- .../primitive/ByAnnotationTest.kt | 45 ++++++------------- 1 file changed, 14 insertions(+), 31 deletions(-) diff --git a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/_integration/ser/value_class/serializer/by_annotation/primitive/ByAnnotationTest.kt b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/_integration/ser/value_class/serializer/by_annotation/primitive/ByAnnotationTest.kt index e15e15f5..262642af 100644 --- a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/_integration/ser/value_class/serializer/by_annotation/primitive/ByAnnotationTest.kt +++ b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/_integration/ser/value_class/serializer/by_annotation/primitive/ByAnnotationTest.kt @@ -5,7 +5,6 @@ import com.fasterxml.jackson.module.kotlin._integration.ser.value_class.serializ import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.fasterxml.jackson.module.kotlin.testPrettyWriter import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertNotEquals import org.junit.jupiter.api.Test class ByAnnotationTest { @@ -14,6 +13,8 @@ class ByAnnotationTest { } data class NonNullSrc( + @JsonSerialize(using = Primitive.Serializer::class) + val paramAnn: Primitive, @get:JsonSerialize(using = Primitive.Serializer::class) val getterAnn: Primitive, @field:JsonSerialize(using = Primitive.Serializer::class) @@ -22,13 +23,14 @@ class ByAnnotationTest { @Test fun nonNull() { - val src = NonNullSrc(Primitive(0), Primitive(1)) + val src = NonNullSrc(Primitive(0), Primitive(1), Primitive(2)) assertEquals( """ { - "getterAnn" : 100, - "fieldAnn" : 101 + "paramAnn" : 100, + "getterAnn" : 101, + "fieldAnn" : 102 } """.trimIndent(), writer.writeValueAsString(src) @@ -36,6 +38,8 @@ class ByAnnotationTest { } data class NullableSrc( + @JsonSerialize(using = Primitive.Serializer::class) + val paramAnn: Primitive?, @get:JsonSerialize(using = Primitive.Serializer::class) val getterAnn: Primitive?, @field:JsonSerialize(using = Primitive.Serializer::class) @@ -44,13 +48,14 @@ class ByAnnotationTest { @Test fun nullableWithoutNull() { - val src = NullableSrc(Primitive(0), Primitive(1)) + val src = NullableSrc(Primitive(0), Primitive(1), Primitive(2)) assertEquals( """ { - "getterAnn" : 100, - "fieldAnn" : 101 + "paramAnn" : 100, + "getterAnn" : 101, + "fieldAnn" : 102 } """.trimIndent(), writer.writeValueAsString(src) @@ -59,11 +64,12 @@ class ByAnnotationTest { @Test fun nullableWithNull() { - val src = NullableSrc(null, null) + val src = NullableSrc(null, null, null) assertEquals( """ { + "paramAnn" : null, "getterAnn" : null, "fieldAnn" : null } @@ -71,27 +77,4 @@ class ByAnnotationTest { writer.writeValueAsString(src) ) } - - data class Failing( - @JsonSerialize(using = Primitive.Serializer::class) - val nonNull: Primitive, - @JsonSerialize(using = Primitive.Serializer::class) - val nullable: Primitive? - ) - - // #46 - @Test - fun failing() { - val src = Failing(Primitive(0), Primitive(1)) - - assertNotEquals( - """ - { - "nonNull" : 100, - "nullable" : 101 - } - """.trimIndent(), - writer.writeValueAsString(src) - ) - } }