From 86b365e30df519210dd82ceefd0bdae4a45f1039 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Wed, 27 Oct 2021 03:02:56 +0900 Subject: [PATCH 01/46] Cut out conditionals and reduce nesting --- .../KotlinNamesAnnotationIntrospector.kt | 85 ++++++++++--------- 1 file changed, 43 insertions(+), 42 deletions(-) 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 5150bfc5..3eb4df3c 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinNamesAnnotationIntrospector.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinNamesAnnotationIntrospector.kt @@ -58,63 +58,64 @@ internal class KotlinNamesAnnotationIntrospector(val module: KotlinModule, val c return null } + // if has parameters, is a Kotlin class, and the parameters all have parameter annotations, then pretend we have a JsonCreator + private fun AnnotatedConstructor.isKotlinConstructorWithParameters(): Boolean = + parameterCount > 0 && declaringClass.isKotlinClass() && !declaringClass.isEnum + @Suppress("UNCHECKED_CAST") override fun hasCreatorAnnotation(member: Annotated): Boolean { // don't add a JsonCreator to any constructor if one is declared already - if (member is AnnotatedConstructor && !member.declaringClass.isEnum) { - // if has parameters, is a Kotlin class, and the parameters all have parameter annotations, then pretend we have a JsonCreator - if (member.parameterCount > 0 && member.declaringClass.isKotlinClass()) { - return cache.checkConstructorIsCreatorAnnotated(member) { - val kClass = cache.kotlinFromJava(member.declaringClass as Class) - val kConstructor = cache.kotlinFromJava(member.annotated as Constructor) + if (member is AnnotatedConstructor && member.isKotlinConstructorWithParameters()) { + return cache.checkConstructorIsCreatorAnnotated(member) { + val kClass = cache.kotlinFromJava(member.declaringClass as Class) + val kConstructor = cache.kotlinFromJava(member.annotated as Constructor) - if (kConstructor != null) { - val isPrimaryConstructor = kClass.primaryConstructor == kConstructor || - (kClass.primaryConstructor == null && kClass.constructors.size == 1) + if (kConstructor != null) { + val isPrimaryConstructor = kClass.primaryConstructor == kConstructor || + (kClass.primaryConstructor == null && kClass.constructors.size == 1) - val propertyNames = kClass.memberProperties.map { it.name }.toSet() + val propertyNames = kClass.memberProperties.map { it.name }.toSet() - fun KFunction<*>.isPossibleSingleString(): Boolean { - val result = parameters.size == 1 && - parameters[0].name !in propertyNames && - parameters[0].type.javaType == String::class.java && - parameters[0].annotations.none { it.annotationClass.java == JsonProperty::class.java } - return result - } + fun KFunction<*>.isPossibleSingleString(): Boolean { + val result = parameters.size == 1 && + parameters[0].name !in propertyNames && + parameters[0].type.javaType == String::class.java && + parameters[0].annotations.none { it.annotationClass.java == JsonProperty::class.java } + return result + } - fun Collection>.filterOutSingleStringCallables(): Collection> { - return this.filter { !it.isPossibleSingleString() } - } + fun Collection>.filterOutSingleStringCallables(): Collection> { + return this.filter { !it.isPossibleSingleString() } + } - val anyConstructorHasJsonCreator = kClass.constructors.filterOutSingleStringCallables() - .any { it.annotations.any { it.annotationClass.java == JsonCreator::class.java } - } + val anyConstructorHasJsonCreator = kClass.constructors.filterOutSingleStringCallables() + .any { it.annotations.any { it.annotationClass.java == JsonCreator::class.java } + } - val anyCompanionMethodIsJsonCreator = member.type.rawClass.kotlin.companionObject?.declaredFunctions - ?.filterOutSingleStringCallables()?.any { - it.annotations.any { it.annotationClass.java == JvmStatic::class.java } && - it.annotations.any { it.annotationClass.java == JsonCreator::class.java } - } ?: false + val anyCompanionMethodIsJsonCreator = member.type.rawClass.kotlin.companionObject?.declaredFunctions + ?.filterOutSingleStringCallables()?.any { + it.annotations.any { it.annotationClass.java == JvmStatic::class.java } && + it.annotations.any { it.annotationClass.java == JsonCreator::class.java } + } ?: false - // TODO: should we do this check or not? It could cause failures if we miss another way a property could be set - // val requiredProperties = kClass.declaredMemberProperties.filter {!it.returnType.isMarkedNullable }.map { it.name }.toSet() - // val areAllRequiredParametersInConstructor = kConstructor.parameters.all { requiredProperties.contains(it.name) } + // TODO: should we do this check or not? It could cause failures if we miss another way a property could be set + // val requiredProperties = kClass.declaredMemberProperties.filter {!it.returnType.isMarkedNullable }.map { it.name }.toSet() + // val areAllRequiredParametersInConstructor = kConstructor.parameters.all { requiredProperties.contains(it.name) } - val areAllParametersValid = kConstructor.parameters.size == kConstructor.parameters.count { it.name != null } + val areAllParametersValid = kConstructor.parameters.size == kConstructor.parameters.count { it.name != null } - val isSingleStringConstructor = kConstructor.isPossibleSingleString() + val isSingleStringConstructor = kConstructor.isPossibleSingleString() - val implyCreatorAnnotation = isPrimaryConstructor - && !(anyConstructorHasJsonCreator || anyCompanionMethodIsJsonCreator) - && areAllParametersValid - && !isSingleStringConstructor - && kClass !in ignoredClassesForImplyingJsonCreator + val implyCreatorAnnotation = isPrimaryConstructor + && !(anyConstructorHasJsonCreator || anyCompanionMethodIsJsonCreator) + && areAllParametersValid + && !isSingleStringConstructor + && kClass !in ignoredClassesForImplyingJsonCreator - implyCreatorAnnotation - } else { - false - } + implyCreatorAnnotation + } else { + false } } } From 854da084fc971136baf6332b6419e4538493772f Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Wed, 27 Oct 2021 03:06:19 +0900 Subject: [PATCH 02/46] Change to if expression --- .../module/kotlin/KotlinNamesAnnotationIntrospector.kt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) 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 3eb4df3c..465a8267 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinNamesAnnotationIntrospector.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinNamesAnnotationIntrospector.kt @@ -66,8 +66,8 @@ internal class KotlinNamesAnnotationIntrospector(val module: KotlinModule, val c override fun hasCreatorAnnotation(member: Annotated): Boolean { // don't add a JsonCreator to any constructor if one is declared already - if (member is AnnotatedConstructor && member.isKotlinConstructorWithParameters()) { - return cache.checkConstructorIsCreatorAnnotated(member) { + return if (member is AnnotatedConstructor && member.isKotlinConstructorWithParameters()) { + cache.checkConstructorIsCreatorAnnotated(member) { val kClass = cache.kotlinFromJava(member.declaringClass as Class) val kConstructor = cache.kotlinFromJava(member.annotated as Constructor) @@ -118,8 +118,7 @@ internal class KotlinNamesAnnotationIntrospector(val module: KotlinModule, val c false } } - } - return false + } else false } @Suppress("UNCHECKED_CAST") From e13bee281acb9228372c9c0066701e3a41dca070 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Wed, 27 Oct 2021 03:11:02 +0900 Subject: [PATCH 03/46] Cut out the isPossibleSingleString function --- .../kotlin/KotlinNamesAnnotationIntrospector.kt | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) 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 465a8267..e46abf51 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinNamesAnnotationIntrospector.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinNamesAnnotationIntrospector.kt @@ -62,6 +62,11 @@ internal class KotlinNamesAnnotationIntrospector(val module: KotlinModule, val c private fun AnnotatedConstructor.isKotlinConstructorWithParameters(): Boolean = parameterCount > 0 && declaringClass.isKotlinClass() && !declaringClass.isEnum + private fun KFunction<*>.isPossibleSingleString(propertyNames: Set): Boolean = parameters.size == 1 && + parameters[0].name !in propertyNames && + parameters[0].type.javaType == String::class.java && + parameters[0].annotations.none { it.annotationClass.java == JsonProperty::class.java } + @Suppress("UNCHECKED_CAST") override fun hasCreatorAnnotation(member: Annotated): Boolean { // don't add a JsonCreator to any constructor if one is declared already @@ -77,16 +82,8 @@ internal class KotlinNamesAnnotationIntrospector(val module: KotlinModule, val c val propertyNames = kClass.memberProperties.map { it.name }.toSet() - fun KFunction<*>.isPossibleSingleString(): Boolean { - val result = parameters.size == 1 && - parameters[0].name !in propertyNames && - parameters[0].type.javaType == String::class.java && - parameters[0].annotations.none { it.annotationClass.java == JsonProperty::class.java } - return result - } - fun Collection>.filterOutSingleStringCallables(): Collection> { - return this.filter { !it.isPossibleSingleString() } + return this.filter { !it.isPossibleSingleString(propertyNames) } } val anyConstructorHasJsonCreator = kClass.constructors.filterOutSingleStringCallables() From 4bd6077cc4f461301a930619beaa22d878b3b674 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Wed, 27 Oct 2021 03:12:11 +0900 Subject: [PATCH 04/46] Kotlinize --- .../module/kotlin/KotlinNamesAnnotationIntrospector.kt | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) 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 e46abf51..f83cdc1f 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinNamesAnnotationIntrospector.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinNamesAnnotationIntrospector.kt @@ -18,10 +18,7 @@ import java.util.* import kotlin.reflect.KClass import kotlin.reflect.KFunction import kotlin.reflect.KParameter -import kotlin.reflect.full.companionObject -import kotlin.reflect.full.declaredFunctions -import kotlin.reflect.full.memberProperties -import kotlin.reflect.full.primaryConstructor +import kotlin.reflect.full.* import kotlin.reflect.jvm.internal.KotlinReflectionInternalError import kotlin.reflect.jvm.javaType import kotlin.reflect.jvm.kotlinFunction @@ -65,7 +62,7 @@ internal class KotlinNamesAnnotationIntrospector(val module: KotlinModule, val c private fun KFunction<*>.isPossibleSingleString(propertyNames: Set): Boolean = parameters.size == 1 && parameters[0].name !in propertyNames && parameters[0].type.javaType == String::class.java && - parameters[0].annotations.none { it.annotationClass.java == JsonProperty::class.java } + !parameters[0].hasAnnotation() @Suppress("UNCHECKED_CAST") override fun hasCreatorAnnotation(member: Annotated): Boolean { From 8d6cb40463652654cd7f96febdac20afae7a5e11 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Wed, 27 Oct 2021 03:13:50 +0900 Subject: [PATCH 05/46] Move to toplevel --- .../KotlinNamesAnnotationIntrospector.kt | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) 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 f83cdc1f..98ad8bf2 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinNamesAnnotationIntrospector.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinNamesAnnotationIntrospector.kt @@ -55,15 +55,6 @@ internal class KotlinNamesAnnotationIntrospector(val module: KotlinModule, val c return null } - // if has parameters, is a Kotlin class, and the parameters all have parameter annotations, then pretend we have a JsonCreator - private fun AnnotatedConstructor.isKotlinConstructorWithParameters(): Boolean = - parameterCount > 0 && declaringClass.isKotlinClass() && !declaringClass.isEnum - - private fun KFunction<*>.isPossibleSingleString(propertyNames: Set): Boolean = parameters.size == 1 && - parameters[0].name !in propertyNames && - parameters[0].type.javaType == String::class.java && - !parameters[0].hasAnnotation() - @Suppress("UNCHECKED_CAST") override fun hasCreatorAnnotation(member: Annotated): Boolean { // don't add a JsonCreator to any constructor if one is declared already @@ -159,3 +150,12 @@ internal class KotlinNamesAnnotationIntrospector(val module: KotlinModule, val c ReplaceWith("with(receiver) { declaringClass.declaredMethods.any { it.name.contains('-') } }") ) private fun AnnotatedMethod.isInlineClass() = declaringClass.declaredMethods.any { it.name.contains('-') } + +// if has parameters, is a Kotlin class, and the parameters all have parameter annotations, then pretend we have a JsonCreator +private fun AnnotatedConstructor.isKotlinConstructorWithParameters(): Boolean = + parameterCount > 0 && declaringClass.isKotlinClass() && !declaringClass.isEnum + +private fun KFunction<*>.isPossibleSingleString(propertyNames: Set): Boolean = parameters.size == 1 && + parameters[0].name !in propertyNames && + parameters[0].type.javaType == String::class.java && + !parameters[0].hasAnnotation() From 80cd2543806f0b95866fbe970479050e90c8ec51 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Wed, 27 Oct 2021 03:18:24 +0900 Subject: [PATCH 06/46] Cut out the filterOutSingleStringCallables function --- .../kotlin/KotlinNamesAnnotationIntrospector.kt | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) 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 98ad8bf2..ee40a425 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinNamesAnnotationIntrospector.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinNamesAnnotationIntrospector.kt @@ -70,16 +70,12 @@ internal class KotlinNamesAnnotationIntrospector(val module: KotlinModule, val c val propertyNames = kClass.memberProperties.map { it.name }.toSet() - fun Collection>.filterOutSingleStringCallables(): Collection> { - return this.filter { !it.isPossibleSingleString(propertyNames) } - } - - val anyConstructorHasJsonCreator = kClass.constructors.filterOutSingleStringCallables() + val anyConstructorHasJsonCreator = kClass.constructors.filterOutSingleStringCallables(propertyNames) .any { it.annotations.any { it.annotationClass.java == JsonCreator::class.java } } val anyCompanionMethodIsJsonCreator = member.type.rawClass.kotlin.companionObject?.declaredFunctions - ?.filterOutSingleStringCallables()?.any { + ?.filterOutSingleStringCallables(propertyNames)?.any { it.annotations.any { it.annotationClass.java == JvmStatic::class.java } && it.annotations.any { it.annotationClass.java == JsonCreator::class.java } } ?: false @@ -90,7 +86,7 @@ internal class KotlinNamesAnnotationIntrospector(val module: KotlinModule, val c val areAllParametersValid = kConstructor.parameters.size == kConstructor.parameters.count { it.name != null } - val isSingleStringConstructor = kConstructor.isPossibleSingleString() + val isSingleStringConstructor = kConstructor.isPossibleSingleString(propertyNames) val implyCreatorAnnotation = isPrimaryConstructor && !(anyConstructorHasJsonCreator || anyCompanionMethodIsJsonCreator) @@ -159,3 +155,6 @@ private fun KFunction<*>.isPossibleSingleString(propertyNames: Set): Boo parameters[0].name !in propertyNames && parameters[0].type.javaType == String::class.java && !parameters[0].hasAnnotation() + +private fun Collection>.filterOutSingleStringCallables(propertyNames: Set): Collection> = + this.filter { !it.isPossibleSingleString(propertyNames) } From f32ffc1c37db491edb6280509a0918b86f57f7d2 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Wed, 27 Oct 2021 03:23:07 +0900 Subject: [PATCH 07/46] kotlinize --- .../kotlin/KotlinNamesAnnotationIntrospector.kt | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) 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 ee40a425..92e1c057 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinNamesAnnotationIntrospector.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinNamesAnnotationIntrospector.kt @@ -70,15 +70,14 @@ internal class KotlinNamesAnnotationIntrospector(val module: KotlinModule, val c val propertyNames = kClass.memberProperties.map { it.name }.toSet() - val anyConstructorHasJsonCreator = kClass.constructors.filterOutSingleStringCallables(propertyNames) - .any { it.annotations.any { it.annotationClass.java == JsonCreator::class.java } - } + val anyConstructorHasJsonCreator = kClass.constructors + .filterOutSingleStringCallables(propertyNames) + .any { it.hasAnnotation() } val anyCompanionMethodIsJsonCreator = member.type.rawClass.kotlin.companionObject?.declaredFunctions - ?.filterOutSingleStringCallables(propertyNames)?.any { - it.annotations.any { it.annotationClass.java == JvmStatic::class.java } && - it.annotations.any { it.annotationClass.java == JsonCreator::class.java } - } ?: false + ?.filterOutSingleStringCallables(propertyNames) + ?.any { it.hasAnnotation() && it.hasAnnotation() } + ?: false // TODO: should we do this check or not? It could cause failures if we miss another way a property could be set // val requiredProperties = kClass.declaredMemberProperties.filter {!it.returnType.isMarkedNullable }.map { it.name }.toSet() From 07d8329f84f9be2e08b23c65fa044826b9d78c66 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Wed, 27 Oct 2021 03:26:35 +0900 Subject: [PATCH 08/46] Remove unnecessary assignments to temporary variables --- .../module/kotlin/KotlinNamesAnnotationIntrospector.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) 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 92e1c057..a34a0c47 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinNamesAnnotationIntrospector.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinNamesAnnotationIntrospector.kt @@ -87,13 +87,11 @@ internal class KotlinNamesAnnotationIntrospector(val module: KotlinModule, val c val isSingleStringConstructor = kConstructor.isPossibleSingleString(propertyNames) - val implyCreatorAnnotation = isPrimaryConstructor + isPrimaryConstructor && !(anyConstructorHasJsonCreator || anyCompanionMethodIsJsonCreator) && areAllParametersValid && !isSingleStringConstructor && kClass !in ignoredClassesForImplyingJsonCreator - - implyCreatorAnnotation } else { false } From 4f9dff5c8523c198a09b8135a46e018d2f72a14a Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Wed, 27 Oct 2021 03:37:20 +0900 Subject: [PATCH 09/46] Cut out a large process into a function --- .../KotlinNamesAnnotationIntrospector.kt | 64 ++++++++++--------- 1 file changed, 33 insertions(+), 31 deletions(-) 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 a34a0c47..bafa9b4d 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinNamesAnnotationIntrospector.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinNamesAnnotationIntrospector.kt @@ -56,49 +56,51 @@ internal class KotlinNamesAnnotationIntrospector(val module: KotlinModule, val c } @Suppress("UNCHECKED_CAST") - override fun hasCreatorAnnotation(member: Annotated): Boolean { + private fun hasCreatorAnnotation(member: AnnotatedConstructor): Boolean { // don't add a JsonCreator to any constructor if one is declared already - return if (member is AnnotatedConstructor && member.isKotlinConstructorWithParameters()) { - cache.checkConstructorIsCreatorAnnotated(member) { - val kClass = cache.kotlinFromJava(member.declaringClass as Class) - val kConstructor = cache.kotlinFromJava(member.annotated as Constructor) + val kClass = cache.kotlinFromJava(member.declaringClass as Class) + val kConstructor = cache.kotlinFromJava(member.annotated as Constructor) - if (kConstructor != null) { - val isPrimaryConstructor = kClass.primaryConstructor == kConstructor || - (kClass.primaryConstructor == null && kClass.constructors.size == 1) + return if (kConstructor != null) { + val isPrimaryConstructor = kClass.primaryConstructor == kConstructor || + (kClass.primaryConstructor == null && kClass.constructors.size == 1) - val propertyNames = kClass.memberProperties.map { it.name }.toSet() + val propertyNames = kClass.memberProperties.map { it.name }.toSet() - val anyConstructorHasJsonCreator = kClass.constructors - .filterOutSingleStringCallables(propertyNames) - .any { it.hasAnnotation() } + val anyConstructorHasJsonCreator = kClass.constructors + .filterOutSingleStringCallables(propertyNames) + .any { it.hasAnnotation() } - val anyCompanionMethodIsJsonCreator = member.type.rawClass.kotlin.companionObject?.declaredFunctions - ?.filterOutSingleStringCallables(propertyNames) - ?.any { it.hasAnnotation() && it.hasAnnotation() } - ?: false + val anyCompanionMethodIsJsonCreator = member.type.rawClass.kotlin.companionObject?.declaredFunctions + ?.filterOutSingleStringCallables(propertyNames) + ?.any { it.hasAnnotation() && it.hasAnnotation() } + ?: false - // TODO: should we do this check or not? It could cause failures if we miss another way a property could be set - // val requiredProperties = kClass.declaredMemberProperties.filter {!it.returnType.isMarkedNullable }.map { it.name }.toSet() - // val areAllRequiredParametersInConstructor = kConstructor.parameters.all { requiredProperties.contains(it.name) } + // TODO: should we do this check or not? It could cause failures if we miss another way a property could be set + // val requiredProperties = kClass.declaredMemberProperties.filter {!it.returnType.isMarkedNullable }.map { it.name }.toSet() + // val areAllRequiredParametersInConstructor = kConstructor.parameters.all { requiredProperties.contains(it.name) } - val areAllParametersValid = kConstructor.parameters.size == kConstructor.parameters.count { it.name != null } + val areAllParametersValid = kConstructor.parameters.size == kConstructor.parameters.count { it.name != null } - val isSingleStringConstructor = kConstructor.isPossibleSingleString(propertyNames) + val isSingleStringConstructor = kConstructor.isPossibleSingleString(propertyNames) - isPrimaryConstructor - && !(anyConstructorHasJsonCreator || anyCompanionMethodIsJsonCreator) - && areAllParametersValid - && !isSingleStringConstructor - && kClass !in ignoredClassesForImplyingJsonCreator - } else { - false - } - } - } else false + isPrimaryConstructor + && !(anyConstructorHasJsonCreator || anyCompanionMethodIsJsonCreator) + && areAllParametersValid + && !isSingleStringConstructor + && kClass !in ignoredClassesForImplyingJsonCreator + } else { + false + } } + override fun hasCreatorAnnotation(member: Annotated): Boolean = + if (member is AnnotatedConstructor && member.isKotlinConstructorWithParameters()) + cache.checkConstructorIsCreatorAnnotated(member) { hasCreatorAnnotation(it) } + else + false + @Suppress("UNCHECKED_CAST") private fun findKotlinParameterName(param: AnnotatedParameter): String? { return if (param.declaringClass.isKotlinClass()) { From 710c63545a8d6cb6f131b54c8d55cd0ff180115d Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Wed, 27 Oct 2021 03:40:52 +0900 Subject: [PATCH 10/46] Change to early return --- .../module/kotlin/KotlinNamesAnnotationIntrospector.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 bafa9b4d..b0e07c8f 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinNamesAnnotationIntrospector.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinNamesAnnotationIntrospector.kt @@ -59,7 +59,9 @@ internal class KotlinNamesAnnotationIntrospector(val module: KotlinModule, val c private fun hasCreatorAnnotation(member: AnnotatedConstructor): Boolean { // don't add a JsonCreator to any constructor if one is declared already - val kClass = cache.kotlinFromJava(member.declaringClass as Class) + val kClass = cache.kotlinFromJava(member.declaringClass as Class).apply { + if (this in ignoredClassesForImplyingJsonCreator) return false + } val kConstructor = cache.kotlinFromJava(member.annotated as Constructor) return if (kConstructor != null) { @@ -89,7 +91,6 @@ internal class KotlinNamesAnnotationIntrospector(val module: KotlinModule, val c && !(anyConstructorHasJsonCreator || anyCompanionMethodIsJsonCreator) && areAllParametersValid && !isSingleStringConstructor - && kClass !in ignoredClassesForImplyingJsonCreator } else { false } From c8a131a759ca85d33271e084fc5dc894baabaedd Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Wed, 27 Oct 2021 03:59:04 +0900 Subject: [PATCH 11/46] Simplify --- .../jackson/module/kotlin/KotlinNamesAnnotationIntrospector.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 b0e07c8f..c7e61394 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinNamesAnnotationIntrospector.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinNamesAnnotationIntrospector.kt @@ -83,7 +83,7 @@ internal class KotlinNamesAnnotationIntrospector(val module: KotlinModule, val c // val requiredProperties = kClass.declaredMemberProperties.filter {!it.returnType.isMarkedNullable }.map { it.name }.toSet() // val areAllRequiredParametersInConstructor = kConstructor.parameters.all { requiredProperties.contains(it.name) } - val areAllParametersValid = kConstructor.parameters.size == kConstructor.parameters.count { it.name != null } + val areAllParametersValid = kConstructor.parameters.all { it.name != null } val isSingleStringConstructor = kConstructor.isPossibleSingleString(propertyNames) From cb6742da6ec0bb0bf5faba44752fe8ee443215ea Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Wed, 27 Oct 2021 04:01:12 +0900 Subject: [PATCH 12/46] modify --- .../module/kotlin/KotlinNamesAnnotationIntrospector.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) 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 c7e61394..46c60e03 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinNamesAnnotationIntrospector.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinNamesAnnotationIntrospector.kt @@ -59,9 +59,8 @@ internal class KotlinNamesAnnotationIntrospector(val module: KotlinModule, val c private fun hasCreatorAnnotation(member: AnnotatedConstructor): Boolean { // don't add a JsonCreator to any constructor if one is declared already - val kClass = cache.kotlinFromJava(member.declaringClass as Class).apply { - if (this in ignoredClassesForImplyingJsonCreator) return false - } + val kClass = cache.kotlinFromJava(member.declaringClass as Class) + .apply { if (this in ignoredClassesForImplyingJsonCreator) return false } val kConstructor = cache.kotlinFromJava(member.annotated as Constructor) return if (kConstructor != null) { From f1050ea3f52226ff7734775620e69effe96021ce Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Wed, 27 Oct 2021 04:02:06 +0900 Subject: [PATCH 13/46] Change to early return --- .../KotlinNamesAnnotationIntrospector.kt | 44 +++++++++---------- 1 file changed, 20 insertions(+), 24 deletions(-) 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 46c60e03..9af1f522 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinNamesAnnotationIntrospector.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinNamesAnnotationIntrospector.kt @@ -61,38 +61,34 @@ internal class KotlinNamesAnnotationIntrospector(val module: KotlinModule, val c val kClass = cache.kotlinFromJava(member.declaringClass as Class) .apply { if (this in ignoredClassesForImplyingJsonCreator) return false } - val kConstructor = cache.kotlinFromJava(member.annotated as Constructor) + val kConstructor = cache.kotlinFromJava(member.annotated as Constructor) ?: return false - return if (kConstructor != null) { - val isPrimaryConstructor = kClass.primaryConstructor == kConstructor || - (kClass.primaryConstructor == null && kClass.constructors.size == 1) + val isPrimaryConstructor = kClass.primaryConstructor == kConstructor || + (kClass.primaryConstructor == null && kClass.constructors.size == 1) - val propertyNames = kClass.memberProperties.map { it.name }.toSet() + val propertyNames = kClass.memberProperties.map { it.name }.toSet() - val anyConstructorHasJsonCreator = kClass.constructors - .filterOutSingleStringCallables(propertyNames) - .any { it.hasAnnotation() } + val anyConstructorHasJsonCreator = kClass.constructors + .filterOutSingleStringCallables(propertyNames) + .any { it.hasAnnotation() } - val anyCompanionMethodIsJsonCreator = member.type.rawClass.kotlin.companionObject?.declaredFunctions - ?.filterOutSingleStringCallables(propertyNames) - ?.any { it.hasAnnotation() && it.hasAnnotation() } - ?: false + val anyCompanionMethodIsJsonCreator = member.type.rawClass.kotlin.companionObject?.declaredFunctions + ?.filterOutSingleStringCallables(propertyNames) + ?.any { it.hasAnnotation() && it.hasAnnotation() } + ?: false - // TODO: should we do this check or not? It could cause failures if we miss another way a property could be set - // val requiredProperties = kClass.declaredMemberProperties.filter {!it.returnType.isMarkedNullable }.map { it.name }.toSet() - // val areAllRequiredParametersInConstructor = kConstructor.parameters.all { requiredProperties.contains(it.name) } + // TODO: should we do this check or not? It could cause failures if we miss another way a property could be set + // val requiredProperties = kClass.declaredMemberProperties.filter {!it.returnType.isMarkedNullable }.map { it.name }.toSet() + // val areAllRequiredParametersInConstructor = kConstructor.parameters.all { requiredProperties.contains(it.name) } - val areAllParametersValid = kConstructor.parameters.all { it.name != null } + val areAllParametersValid = kConstructor.parameters.all { it.name != null } - val isSingleStringConstructor = kConstructor.isPossibleSingleString(propertyNames) + val isSingleStringConstructor = kConstructor.isPossibleSingleString(propertyNames) - isPrimaryConstructor - && !(anyConstructorHasJsonCreator || anyCompanionMethodIsJsonCreator) - && areAllParametersValid - && !isSingleStringConstructor - } else { - false - } + return isPrimaryConstructor + && !(anyConstructorHasJsonCreator || anyCompanionMethodIsJsonCreator) + && areAllParametersValid + && !isSingleStringConstructor } override fun hasCreatorAnnotation(member: Annotated): Boolean = From dbfe25d760575a43eab12417c8fbf640b258e86f Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Wed, 27 Oct 2021 04:13:01 +0900 Subject: [PATCH 14/46] Cut out the judgment process of PrimaryConstructor --- .../module/kotlin/KotlinNamesAnnotationIntrospector.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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 9af1f522..f4485c79 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinNamesAnnotationIntrospector.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinNamesAnnotationIntrospector.kt @@ -63,8 +63,7 @@ internal class KotlinNamesAnnotationIntrospector(val module: KotlinModule, val c .apply { if (this in ignoredClassesForImplyingJsonCreator) return false } val kConstructor = cache.kotlinFromJava(member.annotated as Constructor) ?: return false - val isPrimaryConstructor = kClass.primaryConstructor == kConstructor || - (kClass.primaryConstructor == null && kClass.constructors.size == 1) + val isPrimaryConstructor = kClass.isPrimaryConstructor(kConstructor) val propertyNames = kClass.memberProperties.map { it.name }.toSet() @@ -153,3 +152,6 @@ private fun KFunction<*>.isPossibleSingleString(propertyNames: Set): Boo private fun Collection>.filterOutSingleStringCallables(propertyNames: Set): Collection> = this.filter { !it.isPossibleSingleString(propertyNames) } + +private fun KClass<*>.isPrimaryConstructor(kConstructor: KFunction<*>) = this.primaryConstructor == kConstructor || + (this.primaryConstructor == null && this.constructors.size == 1) From 2780abd313345889456871a785ea24cfdd2978f7 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Wed, 27 Oct 2021 04:20:16 +0900 Subject: [PATCH 15/46] Change to an early return --- .../KotlinNamesAnnotationIntrospector.kt | 38 +++++++++---------- 1 file changed, 18 insertions(+), 20 deletions(-) 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 f4485c79..bb98f5f5 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinNamesAnnotationIntrospector.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinNamesAnnotationIntrospector.kt @@ -63,31 +63,29 @@ internal class KotlinNamesAnnotationIntrospector(val module: KotlinModule, val c .apply { if (this in ignoredClassesForImplyingJsonCreator) return false } val kConstructor = cache.kotlinFromJava(member.annotated as Constructor) ?: return false - val isPrimaryConstructor = kClass.isPrimaryConstructor(kConstructor) - - val propertyNames = kClass.memberProperties.map { it.name }.toSet() - - val anyConstructorHasJsonCreator = kClass.constructors - .filterOutSingleStringCallables(propertyNames) - .any { it.hasAnnotation() } - - val anyCompanionMethodIsJsonCreator = member.type.rawClass.kotlin.companionObject?.declaredFunctions - ?.filterOutSingleStringCallables(propertyNames) - ?.any { it.hasAnnotation() && it.hasAnnotation() } - ?: false - // TODO: should we do this check or not? It could cause failures if we miss another way a property could be set // val requiredProperties = kClass.declaredMemberProperties.filter {!it.returnType.isMarkedNullable }.map { it.name }.toSet() // val areAllRequiredParametersInConstructor = kConstructor.parameters.all { requiredProperties.contains(it.name) } - val areAllParametersValid = kConstructor.parameters.all { it.name != null } - - val isSingleStringConstructor = kConstructor.isPossibleSingleString(propertyNames) + val propertyNames = kClass.memberProperties.map { it.name }.toSet() - return isPrimaryConstructor - && !(anyConstructorHasJsonCreator || anyCompanionMethodIsJsonCreator) - && areAllParametersValid - && !isSingleStringConstructor + return when { + kConstructor.isPossibleSingleString(propertyNames) -> false + kConstructor.parameters.any { it.name == null } -> false + !kClass.isPrimaryConstructor(kConstructor) -> false + else -> { + val anyConstructorHasJsonCreator = kClass.constructors + .filterOutSingleStringCallables(propertyNames) + .any { it.hasAnnotation() } + + val anyCompanionMethodIsJsonCreator = member.type.rawClass.kotlin.companionObject?.declaredFunctions + ?.filterOutSingleStringCallables(propertyNames) + ?.any { it.hasAnnotation() && it.hasAnnotation() } + ?: false + + !(anyConstructorHasJsonCreator || anyCompanionMethodIsJsonCreator) + } + } } override fun hasCreatorAnnotation(member: Annotated): Boolean = From e017f64807f8daecb69076bd3b77e40a00868591 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Wed, 27 Oct 2021 04:25:00 +0900 Subject: [PATCH 16/46] modify --- .../module/kotlin/KotlinNamesAnnotationIntrospector.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 bb98f5f5..fccf8eb3 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinNamesAnnotationIntrospector.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinNamesAnnotationIntrospector.kt @@ -151,5 +151,6 @@ private fun KFunction<*>.isPossibleSingleString(propertyNames: Set): Boo private fun Collection>.filterOutSingleStringCallables(propertyNames: Set): Collection> = this.filter { !it.isPossibleSingleString(propertyNames) } -private fun KClass<*>.isPrimaryConstructor(kConstructor: KFunction<*>) = this.primaryConstructor == kConstructor || - (this.primaryConstructor == null && this.constructors.size == 1) +private fun KClass<*>.isPrimaryConstructor(kConstructor: KFunction<*>) = this.primaryConstructor.let { + it == kConstructor || (it == null && this.constructors.size == 1) +} From 1373c75b753eabbff46192c6fca1f4a9b2120654 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sat, 30 Oct 2021 21:41:03 +0900 Subject: [PATCH 17/46] add Value Creators --- .../module/kotlin/ConstructorValueCreator.kt | 12 ++++ .../module/kotlin/MethodValueCreator.kt | 46 +++++++++++++ .../jackson/module/kotlin/ValueCreator.kt | 64 +++++++++++++++++++ 3 files changed, 122 insertions(+) create mode 100644 src/main/kotlin/com/fasterxml/jackson/module/kotlin/ConstructorValueCreator.kt create mode 100644 src/main/kotlin/com/fasterxml/jackson/module/kotlin/MethodValueCreator.kt create mode 100644 src/main/kotlin/com/fasterxml/jackson/module/kotlin/ValueCreator.kt diff --git a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/ConstructorValueCreator.kt b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/ConstructorValueCreator.kt new file mode 100644 index 00000000..0aaa0a75 --- /dev/null +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/ConstructorValueCreator.kt @@ -0,0 +1,12 @@ +package com.fasterxml.jackson.module.kotlin + +import kotlin.reflect.KFunction +import kotlin.reflect.jvm.isAccessible + +internal class ConstructorValueCreator(override val callable: KFunction) : ValueCreator() { + override val accessible: Boolean = callable.isAccessible + + init { + callable.isAccessible = true + } +} diff --git a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/MethodValueCreator.kt b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/MethodValueCreator.kt new file mode 100644 index 00000000..986ec076 --- /dev/null +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/MethodValueCreator.kt @@ -0,0 +1,46 @@ +package com.fasterxml.jackson.module.kotlin + +import kotlin.reflect.KFunction +import kotlin.reflect.KParameter +import kotlin.reflect.full.extensionReceiverParameter +import kotlin.reflect.full.instanceParameter +import kotlin.reflect.jvm.isAccessible + +internal class MethodValueCreator private constructor( + override val callable: KFunction, + override val accessible: Boolean, + val companionObjectInstance: Any +) : ValueCreator() { + val instanceParameter: KParameter = callable.instanceParameter!! + + companion object { + fun of(callable: KFunction): MethodValueCreator? { + // we shouldn't have an instance or receiver parameter and if we do, just go with default Java-ish behavior + if (callable.extensionReceiverParameter != null) return null + + val possibleCompanion = callable.instanceParameter!!.type.erasedType().kotlin + + // abort, we have some unknown case here + if (!possibleCompanion.isCompanion) return null + + val (companionObjectInstance: Any, accessible: Boolean) = try { + // throws ex + val instance = possibleCompanion.objectInstance!! + // If an instance of the companion object can be obtained, accessibility depends on the KFunction + instance to callable.isAccessible + } catch (ex: IllegalAccessException) { + // fallback for when an odd access exception happens through Kotlin reflection + possibleCompanion.java.enclosingClass.fields + .firstOrNull { it.type.kotlin.isCompanion } + ?.let { + it.isAccessible = true + + // If the instance of the companion object cannot be obtained, accessibility will always be false + it.get(null) to false + } ?: throw ex + } + + return MethodValueCreator(callable, accessible, companionObjectInstance) + } + } +} diff --git a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/ValueCreator.kt b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/ValueCreator.kt new file mode 100644 index 00000000..4c5498ec --- /dev/null +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/ValueCreator.kt @@ -0,0 +1,64 @@ +package com.fasterxml.jackson.module.kotlin + +import com.fasterxml.jackson.databind.DeserializationContext +import com.fasterxml.jackson.databind.MapperFeature +import com.fasterxml.jackson.databind.introspect.AnnotatedConstructor +import com.fasterxml.jackson.databind.introspect.AnnotatedMethod +import com.fasterxml.jackson.databind.introspect.AnnotatedWithParams +import java.lang.reflect.Constructor +import java.lang.reflect.Method +import kotlin.reflect.KFunction +import kotlin.reflect.KParameter +import kotlin.reflect.full.valueParameters + +/** + * A class that abstracts the creation of instances by calling KFunction. + * @see KotlinValueInstantiator + */ +internal sealed class ValueCreator { + /** + * Function to be call. + */ + protected abstract val callable: KFunction + + /** + * Initial value for accessibility by reflection. + */ + protected abstract val accessible: Boolean + + /** + * ValueParameters of the KFunction to be called. + */ + val valueParameters: List by lazy { callable.valueParameters } + + /** + * Checking process to see if access from context is possible. + * @throws IllegalAccessException + */ + fun checkAccessibility(ctxt: DeserializationContext) { + if ((!accessible && ctxt.config.isEnabled(MapperFeature.CAN_OVERRIDE_ACCESS_MODIFIERS)) || + (accessible && ctxt.config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS))) { + return + } + + throw IllegalAccessException("Cannot access to function or companion object instance, target: $callable") + } + + /** + * Function call with default values enabled. + */ + fun callBy(args: Map): T = callable.callBy(args) + + companion object { + @Suppress("UNCHECKED_CAST") + fun of( + _withArgsCreator: AnnotatedWithParams, cache: ReflectionCache + ): ValueCreator<*>? = when (_withArgsCreator) { + is AnnotatedConstructor -> cache.kotlinFromJava(_withArgsCreator.annotated as Constructor) + ?.let { ConstructorValueCreator(it) } + is AnnotatedMethod -> cache.kotlinFromJava(_withArgsCreator.annotated as Method) + ?.let { MethodValueCreator.of(it) } + else -> throw IllegalStateException("Expected a constructor or method to create a Kotlin object, instead found ${_withArgsCreator.annotated.javaClass.name}") + } // we cannot reflect this method so do the default Java-ish behavior + } +} From bf9f0b197ea4d35290f4ff65e0e8bbcce3ef639d Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sun, 31 Oct 2021 00:18:51 +0900 Subject: [PATCH 18/46] use ValueCreator temp --- .../module/kotlin/KotlinValueInstantiator.kt | 90 +++++-------------- 1 file changed, 24 insertions(+), 66 deletions(-) diff --git a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinValueInstantiator.kt b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinValueInstantiator.kt index 2196d968..92e02d27 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinValueInstantiator.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinValueInstantiator.kt @@ -3,24 +3,15 @@ package com.fasterxml.jackson.module.kotlin import com.fasterxml.jackson.databind.BeanDescription import com.fasterxml.jackson.databind.DeserializationConfig import com.fasterxml.jackson.databind.DeserializationContext -import com.fasterxml.jackson.databind.MapperFeature import com.fasterxml.jackson.databind.deser.SettableBeanProperty import com.fasterxml.jackson.databind.deser.ValueInstantiator import com.fasterxml.jackson.databind.deser.ValueInstantiators import com.fasterxml.jackson.databind.deser.impl.NullsAsEmptyProvider import com.fasterxml.jackson.databind.deser.impl.PropertyValueBuffer import com.fasterxml.jackson.databind.deser.std.StdValueInstantiator -import com.fasterxml.jackson.databind.introspect.AnnotatedConstructor -import com.fasterxml.jackson.databind.introspect.AnnotatedMethod -import java.lang.reflect.Constructor -import java.lang.reflect.Method import java.lang.reflect.TypeVariable import kotlin.reflect.KParameter import kotlin.reflect.KType -import kotlin.reflect.full.extensionReceiverParameter -import kotlin.reflect.full.instanceParameter -import kotlin.reflect.full.valueParameters -import kotlin.reflect.jvm.isAccessible import kotlin.reflect.jvm.javaType internal class KotlinValueInstantiator( @@ -31,62 +22,35 @@ internal class KotlinValueInstantiator( private val nullIsSameAsDefault: Boolean, private val strictNullChecks: Boolean ) : StdValueInstantiator(src) { - @Suppress("UNCHECKED_CAST") override fun createFromObjectWith( ctxt: DeserializationContext, props: Array, buffer: PropertyValueBuffer ): Any? { - val callable = when (_withArgsCreator) { - is AnnotatedConstructor -> cache.kotlinFromJava(_withArgsCreator.annotated as Constructor) - is AnnotatedMethod -> cache.kotlinFromJava(_withArgsCreator.annotated as Method) - else -> throw IllegalStateException("Expected a constructor or method to create a Kotlin object, instead found ${_withArgsCreator.annotated.javaClass.name}") - } ?: return super.createFromObjectWith( - ctxt, - props, - buffer - ) // we cannot reflect this method so do the default Java-ish behavior - - if (callable.extensionReceiverParameter != null) { - // we shouldn't have an instance or receiver parameter and if we do, just go with default Java-ish behavior - return super.createFromObjectWith(ctxt, props, buffer) - } - - val propCount = props.size + if (callable.instanceParameter != null) 1 else 0 - - var numCallableParameters = 0 - val callableParameters = arrayOfNulls(propCount) - val jsonParamValueList = arrayOfNulls(propCount) - - if (callable.instanceParameter != null) { - val possibleCompanion = callable.instanceParameter!!.type.erasedType().kotlin - - if (!possibleCompanion.isCompanion) { - // abort, we have some unknown case here - return super.createFromObjectWith(ctxt, props, buffer) - } - - // TODO: cache this lookup since the exception throwing/catching can be expensive - jsonParamValueList[numCallableParameters] = try { - possibleCompanion.objectInstance - } catch (ex: IllegalAccessException) { - // fallback for when an odd access exception happens through Kotlin reflection - val companionField = possibleCompanion.java.enclosingClass.fields.firstOrNull { it.type.kotlin.isCompanion } - ?: throw ex - val accessible = companionField.isAccessible - if ((!accessible && ctxt.config.isEnabled(MapperFeature.CAN_OVERRIDE_ACCESS_MODIFIERS)) || - (accessible && ctxt.config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS)) - ) { - companionField.isAccessible = true - } - companionField.get(null) ?: throw ex - } - - callableParameters[numCallableParameters] = callable.instanceParameter - numCallableParameters++ + // TODO: cache ValueCreator + val valueCreator: ValueCreator<*> = ValueCreator.of(_withArgsCreator, cache) + ?: return super.createFromObjectWith(ctxt, props, buffer) + + val propCount: Int + var numCallableParameters: Int + val callableParameters: Array + val jsonParamValueList: Array + + if (valueCreator is MethodValueCreator) { + propCount = props.size + 1 + numCallableParameters = 1 + callableParameters = arrayOfNulls(propCount) + .apply { this[0] = valueCreator.instanceParameter } + jsonParamValueList = arrayOfNulls(propCount) + .apply { this[0] = valueCreator.companionObjectInstance } + } else { + propCount = props.size + numCallableParameters = 0 + callableParameters = arrayOfNulls(propCount) + jsonParamValueList = arrayOfNulls(propCount) } - callable.valueParameters.forEachIndexed { idx, paramDef -> + valueCreator.valueParameters.forEachIndexed { idx, paramDef -> val jsonProp = props[idx] val isMissing = !buffer.hasParameter(jsonProp) @@ -157,23 +121,17 @@ internal class KotlinValueInstantiator( numCallableParameters++ } - return if (numCallableParameters == jsonParamValueList.size && callable.instanceParameter == null) { + return if (numCallableParameters == jsonParamValueList.size && valueCreator is ConstructorValueCreator) { // we didn't do anything special with default parameters, do a normal call super.createFromObjectWith(ctxt, jsonParamValueList) } else { - val accessible = callable.isAccessible - if ((!accessible && ctxt.config.isEnabled(MapperFeature.CAN_OVERRIDE_ACCESS_MODIFIERS)) || - (accessible && ctxt.config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS)) - ) { - callable.isAccessible = true - } val callableParametersByName = linkedMapOf() callableParameters.mapIndexed { idx, paramDef -> if (paramDef != null) { callableParametersByName[paramDef] = jsonParamValueList[idx] } } - callable.callBy(callableParametersByName) + valueCreator.callBy(callableParametersByName) } } From a1660330761f6d01171791c62f09d4f56ff7effc Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sun, 31 Oct 2021 01:11:32 +0900 Subject: [PATCH 19/46] fix to use cache --- .../module/kotlin/KotlinValueInstantiator.kt | 3 +- .../jackson/module/kotlin/ReflectionCache.kt | 34 +++++++++++++++++++ .../jackson/module/kotlin/ValueCreator.kt | 18 ---------- 3 files changed, 35 insertions(+), 20 deletions(-) diff --git a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinValueInstantiator.kt b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinValueInstantiator.kt index 92e02d27..ab68f707 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinValueInstantiator.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinValueInstantiator.kt @@ -27,8 +27,7 @@ internal class KotlinValueInstantiator( props: Array, buffer: PropertyValueBuffer ): Any? { - // TODO: cache ValueCreator - val valueCreator: ValueCreator<*> = ValueCreator.of(_withArgsCreator, cache) + val valueCreator: ValueCreator<*> = cache.valueCreatorFromJava(_withArgsCreator) ?: return super.createFromObjectWith(ctxt, props, buffer) val propCount: Int diff --git a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/ReflectionCache.kt b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/ReflectionCache.kt index 355a7e95..3b89a05e 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/ReflectionCache.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/ReflectionCache.kt @@ -3,6 +3,7 @@ package com.fasterxml.jackson.module.kotlin import com.fasterxml.jackson.databind.introspect.AnnotatedConstructor import com.fasterxml.jackson.databind.introspect.AnnotatedMember import com.fasterxml.jackson.databind.introspect.AnnotatedMethod +import com.fasterxml.jackson.databind.introspect.AnnotatedWithParams import com.fasterxml.jackson.databind.util.LRUMap import java.lang.reflect.Constructor import java.lang.reflect.Method @@ -35,6 +36,8 @@ internal class ReflectionCache(reflectionCacheSize: Int) { private val javaClassToKotlin = LRUMap, KClass>(reflectionCacheSize, reflectionCacheSize) private val javaConstructorToKotlin = LRUMap, KFunction>(reflectionCacheSize, reflectionCacheSize) private val javaMethodToKotlin = LRUMap>(reflectionCacheSize, reflectionCacheSize) + private val javaConstructorToValueCreator = LRUMap, ConstructorValueCreator<*>>(reflectionCacheSize, reflectionCacheSize) + private val javaMethodToValueCreator = LRUMap>(reflectionCacheSize, reflectionCacheSize) private val javaConstructorIsCreatorAnnotated = LRUMap(reflectionCacheSize, reflectionCacheSize) private val javaMemberIsRequired = LRUMap(reflectionCacheSize, reflectionCacheSize) private val kotlinGeneratedMethod = LRUMap(reflectionCacheSize, reflectionCacheSize) @@ -49,6 +52,37 @@ internal class ReflectionCache(reflectionCacheSize: Int) { fun kotlinFromJava(key: Method): KFunction<*>? = javaMethodToKotlin.get(key) ?: key.kotlinFunction?.let { javaMethodToKotlin.putIfAbsent(key, it) ?: it } + /** + * return null if... + * - can't get kotlinFunction + * - contains extensionReceiverParameter + * - instance parameter is not companion object or can't get + */ + @Suppress("UNCHECKED_CAST") + fun valueCreatorFromJava(_withArgsCreator: AnnotatedWithParams): ValueCreator<*>? = when (_withArgsCreator) { + is AnnotatedConstructor -> { + val constructor = _withArgsCreator.annotated as Constructor + + javaConstructorToValueCreator.get(constructor) + ?: kotlinFromJava(constructor)?.let { + val value = ConstructorValueCreator(it) + + javaConstructorToValueCreator.putIfAbsent(constructor, value) ?: value + } + } + is AnnotatedMethod -> { + val method = _withArgsCreator.annotated as Method + + javaMethodToValueCreator.get(method) + ?: kotlinFromJava(method)?.let { + val value = MethodValueCreator.of(it) + + javaMethodToValueCreator.putIfAbsent(method, value) ?: value + } + } + else -> throw IllegalStateException("Expected a constructor or method to create a Kotlin object, instead found ${_withArgsCreator.annotated.javaClass.name}") + } // we cannot reflect this method so do the default Java-ish behavior + fun checkConstructorIsCreatorAnnotated(key: AnnotatedConstructor, calc: (AnnotatedConstructor) -> Boolean): Boolean = javaConstructorIsCreatorAnnotated.get(key) ?: calc(key).let { javaConstructorIsCreatorAnnotated.putIfAbsent(key, it) ?: it } diff --git a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/ValueCreator.kt b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/ValueCreator.kt index 4c5498ec..cbbddd6c 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/ValueCreator.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/ValueCreator.kt @@ -2,11 +2,6 @@ package com.fasterxml.jackson.module.kotlin import com.fasterxml.jackson.databind.DeserializationContext import com.fasterxml.jackson.databind.MapperFeature -import com.fasterxml.jackson.databind.introspect.AnnotatedConstructor -import com.fasterxml.jackson.databind.introspect.AnnotatedMethod -import com.fasterxml.jackson.databind.introspect.AnnotatedWithParams -import java.lang.reflect.Constructor -import java.lang.reflect.Method import kotlin.reflect.KFunction import kotlin.reflect.KParameter import kotlin.reflect.full.valueParameters @@ -48,17 +43,4 @@ internal sealed class ValueCreator { * Function call with default values enabled. */ fun callBy(args: Map): T = callable.callBy(args) - - companion object { - @Suppress("UNCHECKED_CAST") - fun of( - _withArgsCreator: AnnotatedWithParams, cache: ReflectionCache - ): ValueCreator<*>? = when (_withArgsCreator) { - is AnnotatedConstructor -> cache.kotlinFromJava(_withArgsCreator.annotated as Constructor) - ?.let { ConstructorValueCreator(it) } - is AnnotatedMethod -> cache.kotlinFromJava(_withArgsCreator.annotated as Method) - ?.let { MethodValueCreator.of(it) } - else -> throw IllegalStateException("Expected a constructor or method to create a Kotlin object, instead found ${_withArgsCreator.annotated.javaClass.name}") - } // we cannot reflect this method so do the default Java-ish behavior - } } From 3ffe0a0f1de0284c3fb7ff8138f6fe711cde8853 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sun, 31 Oct 2021 02:18:15 +0900 Subject: [PATCH 20/46] fix --- .../fasterxml/jackson/module/kotlin/KotlinValueInstantiator.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinValueInstantiator.kt b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinValueInstantiator.kt index ab68f707..493e063b 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinValueInstantiator.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinValueInstantiator.kt @@ -124,6 +124,8 @@ internal class KotlinValueInstantiator( // we didn't do anything special with default parameters, do a normal call super.createFromObjectWith(ctxt, jsonParamValueList) } else { + valueCreator.checkAccessibility(ctxt) + val callableParametersByName = linkedMapOf() callableParameters.mapIndexed { idx, paramDef -> if (paramDef != null) { From 8d8086012e6b7ae10921adb24b10f2293eca151b Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sat, 18 Dec 2021 16:39:39 +0900 Subject: [PATCH 21/46] add ValueClassBoxSerializer --- .../jackson/module/kotlin/KotlinSerializers.kt | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinSerializers.kt b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinSerializers.kt index 2ac960bc..e0cee788 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinSerializers.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinSerializers.kt @@ -9,6 +9,7 @@ import com.fasterxml.jackson.databind.SerializerProvider import com.fasterxml.jackson.databind.ser.Serializers import com.fasterxml.jackson.databind.ser.std.StdSerializer import java.math.BigInteger +import kotlin.reflect.KClass object SequenceSerializer : StdSerializer>(Sequence::class.java) { override fun serialize(value: Sequence<*>, gen: JsonGenerator, provider: SerializerProvider) { @@ -71,3 +72,20 @@ internal class KotlinSerializers : Serializers.Base() { else -> null } } + +// This serializer is used to properly serialize the value class. +// The getter generated for the value class is special, +// so this class will not work properly when added to the Serializers +// (it is configured from KotlinAnnotationIntrospector.findSerializer). +internal class ValueClassBoxSerializer( + outerClazz: KClass, innerClazz: Class<*> +) : StdSerializer(outerClazz.java) { + private val boxMethod = _handledType.getMethod("box-impl", innerClazz) + + override fun serialize(value: Any, gen: JsonGenerator, provider: SerializerProvider) { + // Values retrieved from getter are considered validated and constructor-impl is not executed. + val boxed = boxMethod.invoke(null, value) + + provider.findValueSerializer(_handledType).serialize(boxed, gen, provider) + } +} From db939c72e7ecee4df3bd683d47b812f313c02916 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sat, 18 Dec 2021 17:02:55 +0900 Subject: [PATCH 22/46] add findeSerializer --- .../kotlin/KotlinAnnotationIntrospector.kt | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinAnnotationIntrospector.kt b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinAnnotationIntrospector.kt index 918c91d8..7fd3e488 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinAnnotationIntrospector.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinAnnotationIntrospector.kt @@ -11,12 +11,14 @@ import java.lang.reflect.AccessibleObject import java.lang.reflect.Constructor import java.lang.reflect.Field import java.lang.reflect.Method +import kotlin.reflect.KClass import kotlin.reflect.KFunction import kotlin.reflect.KMutableProperty1 import kotlin.reflect.KProperty1 import kotlin.reflect.KType import kotlin.reflect.full.createType import kotlin.reflect.full.declaredMemberProperties +import kotlin.reflect.full.memberProperties import kotlin.reflect.jvm.* @@ -59,6 +61,39 @@ internal class KotlinAnnotationIntrospector(private val context: Module.SetupCon return super.findCreatorAnnotation(config, a) } + // Find a serializer to handle the case where the getter returns an unboxed value from the value class. + override fun findSerializer(am: Annotated): ValueClassBoxSerializer? = when (am) { + is AnnotatedMethod -> { + val getter = am.member.apply { + // If the return value of the getter is a value class, + // it will be serialized properly without doing anything. + if (this.returnType.isUnboxableValueClass()) return null + } + + val kotlinProperty = getter + .declaringClass + .kotlin + .let { + // KotlinReflectionInternalError is raised in GitHub167 test, + // but it looks like an edge case, so it is ignored. + try { + it.memberProperties + } catch (e: Error) { + null + } + }?.find { it.javaGetter == getter } + + (kotlinProperty?.returnType?.classifier as? KClass<*>) + ?.takeIf { it.isValue } + ?.let { outerClazz -> + @Suppress("UNCHECKED_CAST") + ValueClassBoxSerializer(outerClazz as KClass, getter.returnType) + } + } + // Ignore the case of AnnotatedField, because JvmField cannot be set in the field of value class. + else -> null + } + /** * Subclasses can be detected automatically for sealed classes, since all possible subclasses are known * at compile-time to Kotlin. This makes [com.fasterxml.jackson.annotation.JsonSubTypes] redundant. From 19c5c0292720932bc71d55a3c5b2aa5ea2bec6ef Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sat, 18 Dec 2021 17:45:48 +0900 Subject: [PATCH 23/46] added consideration for cases where the value wrapped by value class is null --- .../jackson/module/kotlin/KotlinAnnotationIntrospector.kt | 3 +++ .../com/fasterxml/jackson/module/kotlin/KotlinSerializers.kt | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinAnnotationIntrospector.kt b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinAnnotationIntrospector.kt index 7fd3e488..6e152883 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinAnnotationIntrospector.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinAnnotationIntrospector.kt @@ -94,6 +94,9 @@ internal class KotlinAnnotationIntrospector(private val context: Module.SetupCon else -> null } + // Perform proper serialization even if the value wrapped by the value class is null. + override fun findNullSerializer(am: Annotated) = findSerializer(am) + /** * Subclasses can be detected automatically for sealed classes, since all possible subclasses are known * at compile-time to Kotlin. This makes [com.fasterxml.jackson.annotation.JsonSubTypes] redundant. diff --git a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinSerializers.kt b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinSerializers.kt index e0cee788..12b65f6e 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinSerializers.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinSerializers.kt @@ -82,7 +82,7 @@ internal class ValueClassBoxSerializer( ) : StdSerializer(outerClazz.java) { private val boxMethod = _handledType.getMethod("box-impl", innerClazz) - override fun serialize(value: Any, gen: JsonGenerator, provider: SerializerProvider) { + override fun serialize(value: Any?, gen: JsonGenerator, provider: SerializerProvider) { // Values retrieved from getter are considered validated and constructor-impl is not executed. val boxed = boxMethod.invoke(null, value) From 2de548afc85041f978e94cecdd70cedc06a91931 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sat, 18 Dec 2021 17:57:06 +0900 Subject: [PATCH 24/46] add test --- .../module/kotlin/test/github/GitHub524.kt | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/github/GitHub524.kt diff --git a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/github/GitHub524.kt b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/github/GitHub524.kt new file mode 100644 index 00000000..29d7c2a9 --- /dev/null +++ b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/github/GitHub524.kt @@ -0,0 +1,55 @@ +package com.fasterxml.jackson.module.kotlin.test.github + +import com.fasterxml.jackson.core.JsonGenerator +import com.fasterxml.jackson.databind.SerializerProvider +import com.fasterxml.jackson.databind.module.SimpleModule +import com.fasterxml.jackson.databind.ser.std.StdSerializer +import com.fasterxml.jackson.module.kotlin.jacksonMapperBuilder +import org.junit.Test +import kotlin.test.assertEquals + +// Most of the current behavior has been tested on GitHub464, so only serializer-related behavior is tested here. +class GitHub524 { + @JvmInline + value class HasSerializer(val value: Int?) + object Serializer : StdSerializer(HasSerializer::class.java) { + override fun serialize(value: HasSerializer, gen: JsonGenerator, provider: SerializerProvider) { + gen.writeString(value.toString()) + } + } + + @JvmInline + value class NoSerializer(val value: Int?) + + data class Poko( + // ULong has a custom serializer defined in Serializers. + val foo: ULong = ULong.MAX_VALUE, + // If a custom serializer is set, the ValueClassUnboxSerializer will be overridden. + val bar: HasSerializer = HasSerializer(1), + val baz: HasSerializer = HasSerializer(null), + val qux: HasSerializer? = null, + // If there is no serializer, it will be unboxed as the existing. + val quux: NoSerializer = NoSerializer(2) + ) + + @Test + fun test() { + val sm = SimpleModule() + .addSerializer(Serializer) + val writer = jacksonMapperBuilder().addModule(sm).build().writerWithDefaultPrettyPrinter() + + // 18446744073709551615 is ULong.MAX_VALUE. + assertEquals( + """ + { + "foo" : 18446744073709551615, + "bar" : "HasSerializer(value=1)", + "baz" : "HasSerializer(value=null)", + "qux" : null, + "quux" : 2 + } + """.trimIndent(), + writer.writeValueAsString(Poko()) + ) + } +} From e4fd10d050ed21ccc1f1ca402d5495141971c197 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sat, 18 Dec 2021 18:33:12 +0900 Subject: [PATCH 25/46] add failing test --- .../module/kotlin/test/github/GitHub524.kt | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/github/GitHub524.kt b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/github/GitHub524.kt index 29d7c2a9..b1c7c80b 100644 --- a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/github/GitHub524.kt +++ b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/github/GitHub524.kt @@ -2,17 +2,20 @@ package com.fasterxml.jackson.module.kotlin.test.github import com.fasterxml.jackson.core.JsonGenerator import com.fasterxml.jackson.databind.SerializerProvider +import com.fasterxml.jackson.databind.annotation.JsonSerialize import com.fasterxml.jackson.databind.module.SimpleModule import com.fasterxml.jackson.databind.ser.std.StdSerializer import com.fasterxml.jackson.module.kotlin.jacksonMapperBuilder +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import org.junit.Test import kotlin.test.assertEquals +import kotlin.test.assertNotEquals // Most of the current behavior has been tested on GitHub464, so only serializer-related behavior is tested here. class GitHub524 { @JvmInline value class HasSerializer(val value: Int?) - object Serializer : StdSerializer(HasSerializer::class.java) { + class Serializer : StdSerializer(HasSerializer::class.java) { override fun serialize(value: HasSerializer, gen: JsonGenerator, provider: SerializerProvider) { gen.writeString(value.toString()) } @@ -35,7 +38,7 @@ class GitHub524 { @Test fun test() { val sm = SimpleModule() - .addSerializer(Serializer) + .addSerializer(Serializer()) val writer = jacksonMapperBuilder().addModule(sm).build().writerWithDefaultPrettyPrinter() // 18446744073709551615 is ULong.MAX_VALUE. @@ -52,4 +55,21 @@ class GitHub524 { writer.writeValueAsString(Poko()) ) } + + class SerializeByAnnotation(@get:JsonSerialize(using = Serializer::class) val foo: HasSerializer = HasSerializer(1)) + + @Test + fun failing() { + val writer = jacksonObjectMapper().writerWithDefaultPrettyPrinter() + + // JsonSerialize is not working now. + assertNotEquals( + """ + { + "foo" : "HasSerializer(value=1)" + } + """.trimIndent(), + writer.writeValueAsString(SerializeByAnnotation()) + ) + } } From eb38c62068ea1083cf066d951d8f2dd71891129d Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sat, 18 Dec 2021 19:16:59 +0900 Subject: [PATCH 26/46] add CREDITS --- release-notes/CREDITS-2.x | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes/CREDITS-2.x b/release-notes/CREDITS-2.x index 309b593f..c4cef506 100644 --- a/release-notes/CREDITS-2.x +++ b/release-notes/CREDITS-2.x @@ -22,6 +22,7 @@ wrongwrong (k163377@github) * #456: Refactor KNAI.findImplicitPropertyName() * #449: Refactor AnnotatedMethod.hasRequiredMarker() * #521: Fixed lookup of instantiators +* #527: Improvements to serialization of `value class`. Dmitri Domanine (novtor@github) * Contributed fix for #490: Missing value of type JsonNode? is deserialized as NullNode instead of null From 1660b948737eea6351804d6b47cf2892be41e884 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Fri, 31 Dec 2021 10:13:35 +0900 Subject: [PATCH 27/46] fix generics --- .../module/kotlin/KotlinAnnotationIntrospector.kt | 5 +++-- .../jackson/module/kotlin/KotlinSerializers.kt | 12 ++++++------ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinAnnotationIntrospector.kt b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinAnnotationIntrospector.kt index 6e152883..bb99dd8e 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinAnnotationIntrospector.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinAnnotationIntrospector.kt @@ -62,7 +62,7 @@ internal class KotlinAnnotationIntrospector(private val context: Module.SetupCon } // Find a serializer to handle the case where the getter returns an unboxed value from the value class. - override fun findSerializer(am: Annotated): ValueClassBoxSerializer? = when (am) { + override fun findSerializer(am: Annotated): ValueClassBoxSerializer<*>? = when (am) { is AnnotatedMethod -> { val getter = am.member.apply { // If the return value of the getter is a value class, @@ -85,9 +85,10 @@ internal class KotlinAnnotationIntrospector(private val context: Module.SetupCon (kotlinProperty?.returnType?.classifier as? KClass<*>) ?.takeIf { it.isValue } + ?.java ?.let { outerClazz -> @Suppress("UNCHECKED_CAST") - ValueClassBoxSerializer(outerClazz as KClass, getter.returnType) + ValueClassBoxSerializer(outerClazz, getter.returnType) } } // Ignore the case of AnnotatedField, because JvmField cannot be set in the field of value class. diff --git a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinSerializers.kt b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinSerializers.kt index 12b65f6e..2c27cc7f 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinSerializers.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinSerializers.kt @@ -77,15 +77,15 @@ internal class KotlinSerializers : Serializers.Base() { // The getter generated for the value class is special, // so this class will not work properly when added to the Serializers // (it is configured from KotlinAnnotationIntrospector.findSerializer). -internal class ValueClassBoxSerializer( - outerClazz: KClass, innerClazz: Class<*> -) : StdSerializer(outerClazz.java) { - private val boxMethod = _handledType.getMethod("box-impl", innerClazz) +internal class ValueClassBoxSerializer( + private val outerClazz: Class, innerClazz: Class +) : StdSerializer(innerClazz) { + private val boxMethod = outerClazz.getMethod("box-impl", innerClazz) - override fun serialize(value: Any?, gen: JsonGenerator, provider: SerializerProvider) { + override fun serialize(value: T?, gen: JsonGenerator, provider: SerializerProvider) { // Values retrieved from getter are considered validated and constructor-impl is not executed. val boxed = boxMethod.invoke(null, value) - provider.findValueSerializer(_handledType).serialize(boxed, gen, provider) + provider.findValueSerializer(outerClazz).serialize(boxed, gen, provider) } } From 9cd05ee129b046322db3d5dd7eab3e31883d050d Mon Sep 17 00:00:00 2001 From: Drew Stephens Date: Sat, 1 Jan 2022 16:26:41 -0500 Subject: [PATCH 28/46] Add exclude for altered class signature --- pom.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pom.xml b/pom.xml index 54b2edd7..91dbace1 100644 --- a/pom.xml +++ b/pom.xml @@ -204,6 +204,10 @@ true true + + com.fasterxml.jackson.module.kotlin.ValueClassBoxSerializer From cc2a337deecf4f66040d1702a97de1734a78d843 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sun, 2 Jan 2022 15:16:36 +0900 Subject: [PATCH 29/46] add tests --- .../module/kotlin/test/github/GitHub530.kt | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/github/GitHub530.kt diff --git a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/github/GitHub530.kt b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/github/GitHub530.kt new file mode 100644 index 00000000..6bacaa2c --- /dev/null +++ b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/github/GitHub530.kt @@ -0,0 +1,83 @@ +package com.fasterxml.jackson.module.kotlin.test.github + +import com.fasterxml.jackson.annotation.JsonValue +import com.fasterxml.jackson.module.kotlin.jacksonMapperBuilder +import org.junit.Test +import kotlin.test.assertEquals + +class GitHub530 { + // At the moment, the output is the same with or without `JsonValue`, + // but this pattern is included in the test case in case the option to default to a serialization method that + // does not `unbox` is introduced in the future. + @JvmInline value class Foo(@get:JsonValue val value: Int) + @JvmInline value class Bar(@JvmField @field:JsonValue val value: Int) + + @JvmInline value class Baz(val value: Int) { + @get:JsonValue val jsonValue: String get() = this.toString() + } + @JvmInline value class Qux(val value: Int) { + @JsonValue fun getJsonValue(): String = this.toString() + } + + interface JsonValueGetter { @get:JsonValue val jsonValue: String get() = this.toString() } + @JvmInline value class Quux(val value: Int): JsonValueGetter + @JvmInline value class Corge(val value: Int): JsonValueGetter + + @JvmInline value class Grault(val value: Int) { + @get:JsonValue val jsonValue: String get() = this.toString() + } + + data class Data( + val foo1: Foo, + val foo2: Foo?, + val bar1: Bar, + val bar2: Bar?, + val baz1: Baz, + val baz2: Baz?, + val qux1: Qux, + val qux2: Qux?, + val quux1: Quux, + val quux2: Quux?, + val corge1: JsonValueGetter, + val corge2: JsonValueGetter?, + val grault1: T, + val grault2: T? + ) + + @Test + fun test() { + val writer = jacksonMapperBuilder().build().writerWithDefaultPrettyPrinter() + + assertEquals( + """ + { + "foo1" : 0, + "foo2" : 1, + "bar1" : 2, + "bar2" : 3, + "baz1" : "Baz(value=4)", + "baz2" : "Baz(value=5)", + "qux1" : "Qux(value=6)", + "qux2" : "Qux(value=7)", + "quux1" : "Quux(value=8)", + "quux2" : "Quux(value=9)", + "corge1" : "Corge(value=10)", + "corge2" : "Corge(value=11)", + "grault1" : "Grault(value=12)", + "grault2" : "Grault(value=13)", + } + """.trimIndent(), + writer.writeValueAsString( + Data( + Foo(0), Foo(1), + Bar(2), Bar(3), + Baz(4), Baz(5), + Qux(6), Qux(7), + Quux(8), Quux(9), + Corge(10), Corge(11), + Grault(12), Grault(13) + ) + ) + ) + } +} From b1cd3cff50e6fd1a108c7f4fec390703b8bb326a Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sun, 2 Jan 2022 15:39:05 +0900 Subject: [PATCH 30/46] fix for unboxed cases --- .../kotlin/KotlinAnnotationIntrospector.kt | 9 ++++-- .../module/kotlin/KotlinSerializers.kt | 32 ++++++++++++++++++- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinAnnotationIntrospector.kt b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinAnnotationIntrospector.kt index bb99dd8e..f674699f 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinAnnotationIntrospector.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinAnnotationIntrospector.kt @@ -7,6 +7,7 @@ import com.fasterxml.jackson.databind.Module import com.fasterxml.jackson.databind.cfg.MapperConfig import com.fasterxml.jackson.databind.introspect.* import com.fasterxml.jackson.databind.jsontype.NamedType +import com.fasterxml.jackson.databind.ser.std.StdSerializer import java.lang.reflect.AccessibleObject import java.lang.reflect.Constructor import java.lang.reflect.Field @@ -62,7 +63,7 @@ internal class KotlinAnnotationIntrospector(private val context: Module.SetupCon } // Find a serializer to handle the case where the getter returns an unboxed value from the value class. - override fun findSerializer(am: Annotated): ValueClassBoxSerializer<*>? = when (am) { + override fun findSerializer(am: Annotated): StdSerializer<*>? = when (am) { is AnnotatedMethod -> { val getter = am.member.apply { // If the return value of the getter is a value class, @@ -87,8 +88,10 @@ internal class KotlinAnnotationIntrospector(private val context: Module.SetupCon ?.takeIf { it.isValue } ?.java ?.let { outerClazz -> - @Suppress("UNCHECKED_CAST") - ValueClassBoxSerializer(outerClazz, getter.returnType) + val innerClazz = getter.returnType + + ValueClassStaticJsonValueSerializer.createdOrNull(outerClazz, innerClazz) + ?: @Suppress("UNCHECKED_CAST") ValueClassBoxSerializer(outerClazz, innerClazz) } } // Ignore the case of AnnotatedField, because JvmField cannot be set in the field of value class. diff --git a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinSerializers.kt b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinSerializers.kt index 2c27cc7f..801ba7d8 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinSerializers.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinSerializers.kt @@ -1,5 +1,6 @@ package com.fasterxml.jackson.module.kotlin +import com.fasterxml.jackson.annotation.JsonValue import com.fasterxml.jackson.core.JsonGenerator import com.fasterxml.jackson.databind.BeanDescription import com.fasterxml.jackson.databind.JavaType @@ -8,8 +9,9 @@ import com.fasterxml.jackson.databind.SerializationConfig import com.fasterxml.jackson.databind.SerializerProvider import com.fasterxml.jackson.databind.ser.Serializers import com.fasterxml.jackson.databind.ser.std.StdSerializer +import java.lang.reflect.Method +import java.lang.reflect.Modifier import java.math.BigInteger -import kotlin.reflect.KClass object SequenceSerializer : StdSerializer>(Sequence::class.java) { override fun serialize(value: Sequence<*>, gen: JsonGenerator, provider: SerializerProvider) { @@ -43,6 +45,10 @@ object ULongSerializer : StdSerializer(ULong::class.java) { } } +// Class must be UnboxableValueClass. +private fun Class<*>.getStaticJsonValueGetter(): Method? = this.declaredMethods + .find { method -> Modifier.isStatic(method.modifiers) && method.annotations.any { it is JsonValue } } + object ValueClassUnboxSerializer : StdSerializer(Any::class.java) { override fun serialize(value: Any, gen: JsonGenerator, provider: SerializerProvider) { val unboxed = value::class.java.getMethod("unbox-impl").invoke(value) @@ -89,3 +95,27 @@ internal class ValueClassBoxSerializer( provider.findValueSerializer(outerClazz).serialize(boxed, gen, provider) } } + +internal class ValueClassStaticJsonValueSerializer private constructor( + innerClazz: Class, + private val staticJsonValueGetter: Method +) : StdSerializer(innerClazz) { + override fun serialize(value: T?, gen: JsonGenerator, provider: SerializerProvider) { + // As shown in the processing of the factory function, jsonValueGetter is always a static method. + val jsonValue: Any? = staticJsonValueGetter.invoke(null, value) + jsonValue + ?.let { provider.findValueSerializer(it::class.java).serialize(it, gen, provider) } + ?: provider.findNullValueSerializer(null).serialize(null, gen, provider) + } + + // Since JsonValue can be processed correctly if it is given to a non-static getter/field, + // this class will only process if it is a `static` method. + companion object { + fun createdOrNull( + outerClazz: Class, + innerClazz: Class + ): ValueClassStaticJsonValueSerializer? = outerClazz + .getStaticJsonValueGetter() + ?.let { ValueClassStaticJsonValueSerializer(innerClazz, it) } + } +} From 02fea55459f89983492ca9256b1d24bfea3455e1 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sun, 2 Jan 2022 15:51:48 +0900 Subject: [PATCH 31/46] fix test --- .../fasterxml/jackson/module/kotlin/test/github/GitHub530.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/github/GitHub530.kt b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/github/GitHub530.kt index 6bacaa2c..831ad2de 100644 --- a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/github/GitHub530.kt +++ b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/github/GitHub530.kt @@ -64,7 +64,7 @@ class GitHub530 { "corge1" : "Corge(value=10)", "corge2" : "Corge(value=11)", "grault1" : "Grault(value=12)", - "grault2" : "Grault(value=13)", + "grault2" : "Grault(value=13)" } """.trimIndent(), writer.writeValueAsString( From 756d5448ee7e4a32d952766c62475f6b86700246 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sun, 2 Jan 2022 15:59:48 +0900 Subject: [PATCH 32/46] fix for boxed cases --- .../module/kotlin/KotlinSerializers.kt | 43 +++++++++++++++---- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinSerializers.kt b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinSerializers.kt index 801ba7d8..a5aea09a 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinSerializers.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinSerializers.kt @@ -49,16 +49,43 @@ object ULongSerializer : StdSerializer(ULong::class.java) { private fun Class<*>.getStaticJsonValueGetter(): Method? = this.declaredMethods .find { method -> Modifier.isStatic(method.modifiers) && method.annotations.any { it is JsonValue } } -object ValueClassUnboxSerializer : StdSerializer(Any::class.java) { - override fun serialize(value: Any, gen: JsonGenerator, provider: SerializerProvider) { - val unboxed = value::class.java.getMethod("unbox-impl").invoke(value) +internal sealed class ValueClassSerializer(t: Class) : StdSerializer(t) { + object Unbox : ValueClassSerializer(Any::class.java) { + override fun serialize(value: Any, gen: JsonGenerator, provider: SerializerProvider) { + val unboxed = value::class.java.getMethod("unbox-impl").invoke(value) - if (unboxed == null) { - provider.findNullValueSerializer(null).serialize(unboxed, gen, provider) - return + if (unboxed == null) { + provider.findNullValueSerializer(null).serialize(unboxed, gen, provider) + return + } + + provider.findValueSerializer(unboxed::class.java).serialize(unboxed, gen, provider) + } + } + + class StaticJsonValue( + t: Class, private val staticJsonValueGetter: Method + ) : ValueClassSerializer(t) { + private val unboxMethod: Method = t.getMethod("unbox-impl") + + override fun serialize(value: T, gen: JsonGenerator, provider: SerializerProvider) { + val unboxed = unboxMethod.invoke(value) + // As shown in the processing of the factory function, jsonValueGetter is always a static method. + val jsonValue: Any? = staticJsonValueGetter.invoke(null, unboxed) + jsonValue + ?.let { provider.findValueSerializer(it::class.java).serialize(it, gen, provider) } + ?: provider.findNullValueSerializer(null).serialize(null, gen, provider) } + } - provider.findValueSerializer(unboxed::class.java).serialize(unboxed, gen, provider) + companion object { + // `t` must be UnboxableValueClass. + // If create a function with a JsonValue in the value class, + // it will be compiled as a static method (= cannot be processed properly by Jackson), + // so use a ValueClassSerializer.StaticJsonValue to handle this. + fun from(t: Class<*>): ValueClassSerializer<*> = t.getStaticJsonValueGetter() + ?.let { StaticJsonValue(t, it) } + ?: Unbox } } @@ -74,7 +101,7 @@ internal class KotlinSerializers : Serializers.Base() { UInt::class.java.isAssignableFrom(type.rawClass) -> UIntSerializer ULong::class.java.isAssignableFrom(type.rawClass) -> ULongSerializer // The priority of Unboxing needs to be lowered so as not to break the serialization of Unsigned Integers. - type.rawClass.isUnboxableValueClass() -> ValueClassUnboxSerializer + type.rawClass.isUnboxableValueClass() -> ValueClassSerializer.from(type.rawClass) else -> null } } From 14a1244c6011c8863f7433c60369400315956d1e Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sun, 2 Jan 2022 16:01:23 +0900 Subject: [PATCH 33/46] refactor --- .../module/kotlin/KotlinSerializers.kt | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinSerializers.kt b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinSerializers.kt index a5aea09a..305de370 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinSerializers.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinSerializers.kt @@ -94,15 +94,19 @@ internal class KotlinSerializers : Serializers.Base() { config: SerializationConfig?, type: JavaType, beanDesc: BeanDescription? - ): JsonSerializer<*>? = when { - Sequence::class.java.isAssignableFrom(type.rawClass) -> SequenceSerializer - UByte::class.java.isAssignableFrom(type.rawClass) -> UByteSerializer - UShort::class.java.isAssignableFrom(type.rawClass) -> UShortSerializer - UInt::class.java.isAssignableFrom(type.rawClass) -> UIntSerializer - ULong::class.java.isAssignableFrom(type.rawClass) -> ULongSerializer - // The priority of Unboxing needs to be lowered so as not to break the serialization of Unsigned Integers. - type.rawClass.isUnboxableValueClass() -> ValueClassSerializer.from(type.rawClass) - else -> null + ): JsonSerializer<*>? { + val rawClass = type.rawClass + + return when { + Sequence::class.java.isAssignableFrom(rawClass) -> SequenceSerializer + UByte::class.java.isAssignableFrom(rawClass) -> UByteSerializer + UShort::class.java.isAssignableFrom(rawClass) -> UShortSerializer + UInt::class.java.isAssignableFrom(rawClass) -> UIntSerializer + ULong::class.java.isAssignableFrom(rawClass) -> ULongSerializer + // The priority of Unboxing needs to be lowered so as not to break the serialization of Unsigned Integers. + rawClass.isUnboxableValueClass() -> ValueClassSerializer.from(rawClass) + else -> null + } } } From a51e1a2b9d21af619a85ef728f980bcc4d75943b Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sun, 2 Jan 2022 16:19:29 +0900 Subject: [PATCH 34/46] add test cases --- .../module/kotlin/test/github/GitHub530.kt | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/github/GitHub530.kt b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/github/GitHub530.kt index 831ad2de..a4106b68 100644 --- a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/github/GitHub530.kt +++ b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/github/GitHub530.kt @@ -45,7 +45,7 @@ class GitHub530 { ) @Test - fun test() { + fun inProperty() { val writer = jacksonMapperBuilder().build().writerWithDefaultPrettyPrinter() assertEquals( @@ -80,4 +80,24 @@ class GitHub530 { ) ) } + + @Test + fun inCollection() { + val writer = jacksonMapperBuilder().build().writerWithDefaultPrettyPrinter() + + assertEquals( + "[ 0, 1, \"Baz(value=2)\", \"Qux(value=3)\", \"Quux(value=4)\" ]", + writer.writeValueAsString(listOf(Foo(0), Bar(1), Baz(2), Qux(3), Quux(4))) + ) + } + + @Test + fun inArray() { + val writer = jacksonMapperBuilder().build().writerWithDefaultPrettyPrinter() + + assertEquals( + "[ 0, 1, \"Baz(value=2)\", \"Qux(value=3)\", \"Quux(value=4)\" ]", + writer.writeValueAsString(arrayOf(Foo(0), Bar(1), Baz(2), Qux(3), Quux(4))) + ) + } } From 459241842885ccfd30f48a63e3e012092382c6e8 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Mon, 3 Jan 2022 07:43:29 +0900 Subject: [PATCH 35/46] Update src/main/kotlin/com/fasterxml/jackson/module/kotlin/ReflectionCache.kt Co-authored-by: Drew Stephens --- .../com/fasterxml/jackson/module/kotlin/ReflectionCache.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/ReflectionCache.kt b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/ReflectionCache.kt index 3b89a05e..fb04a63f 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/ReflectionCache.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/ReflectionCache.kt @@ -76,7 +76,6 @@ internal class ReflectionCache(reflectionCacheSize: Int) { javaMethodToValueCreator.get(method) ?: kotlinFromJava(method)?.let { val value = MethodValueCreator.of(it) - javaMethodToValueCreator.putIfAbsent(method, value) ?: value } } From 986e7a633fb64bf95b72a4b2326ad5f28d83fcbd Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Mon, 3 Jan 2022 07:44:52 +0900 Subject: [PATCH 36/46] Update src/main/kotlin/com/fasterxml/jackson/module/kotlin/ReflectionCache.kt Co-authored-by: Drew Stephens --- .../com/fasterxml/jackson/module/kotlin/ReflectionCache.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/ReflectionCache.kt b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/ReflectionCache.kt index fb04a63f..432b8518 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/ReflectionCache.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/ReflectionCache.kt @@ -66,7 +66,6 @@ internal class ReflectionCache(reflectionCacheSize: Int) { javaConstructorToValueCreator.get(constructor) ?: kotlinFromJava(constructor)?.let { val value = ConstructorValueCreator(it) - javaConstructorToValueCreator.putIfAbsent(constructor, value) ?: value } } From c0ab03802a22a6fb7b078d1d0db89da006fd4824 Mon Sep 17 00:00:00 2001 From: Drew Stephens Date: Mon, 3 Jan 2022 06:26:49 -0500 Subject: [PATCH 37/46] Split out separate tests --- .../module/kotlin/test/github/GitHub530.kt | 249 +++++++++++++----- 1 file changed, 190 insertions(+), 59 deletions(-) diff --git a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/github/GitHub530.kt b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/github/GitHub530.kt index a4106b68..99139ea4 100644 --- a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/github/GitHub530.kt +++ b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/github/GitHub530.kt @@ -2,102 +2,233 @@ package com.fasterxml.jackson.module.kotlin.test.github import com.fasterxml.jackson.annotation.JsonValue import com.fasterxml.jackson.module.kotlin.jacksonMapperBuilder -import org.junit.Test import kotlin.test.assertEquals +import org.junit.Test class GitHub530 { // At the moment, the output is the same with or without `JsonValue`, // but this pattern is included in the test case in case the option to default to a serialization method that // does not `unbox` is introduced in the future. - @JvmInline value class Foo(@get:JsonValue val value: Int) - @JvmInline value class Bar(@JvmField @field:JsonValue val value: Int) + @JvmInline + value class ValueParamGetterAnnotated(@get:JsonValue val value: Int) - @JvmInline value class Baz(val value: Int) { - @get:JsonValue val jsonValue: String get() = this.toString() + @JvmInline + value class ValueParamFieldAnnotated(@JvmField @field:JsonValue val value: Int) + + @JvmInline + value class PropertyWithOverriddenGetter(val value: Int) { + @get:JsonValue + val jsonValue: String + get() = this.toString() } - @JvmInline value class Qux(val value: Int) { - @JsonValue fun getJsonValue(): String = this.toString() + + @JvmInline + value class DirectlyOverriddenGetter(val value: Int) { + @JsonValue + fun getJsonValue(): String = this.toString() } - interface JsonValueGetter { @get:JsonValue val jsonValue: String get() = this.toString() } - @JvmInline value class Quux(val value: Int): JsonValueGetter - @JvmInline value class Corge(val value: Int): JsonValueGetter + interface JsonValueGetter { + @get:JsonValue + val jsonValue: String + get() = this.toString() + } + + @JvmInline + value class JsonValueGetterImplementation(val value: Int) : JsonValueGetter + + private val writer = jacksonMapperBuilder().build().writerWithDefaultPrettyPrinter() - @JvmInline value class Grault(val value: Int) { - @get:JsonValue val jsonValue: String get() = this.toString() + @Test + fun valueParamGetterAnnotated() { + data class Data( + val nonNull: ValueParamGetterAnnotated, + val nullable: ValueParamGetterAnnotated? + ) + + assertEquals( + """ + { + "nonNull" : 0, + "nullable" : 1 + } + """.trimIndent(), + writer.writeValueAsString( + Data( + ValueParamGetterAnnotated(0), + ValueParamGetterAnnotated(1) + ) + ) + ) } - data class Data( - val foo1: Foo, - val foo2: Foo?, - val bar1: Bar, - val bar2: Bar?, - val baz1: Baz, - val baz2: Baz?, - val qux1: Qux, - val qux2: Qux?, - val quux1: Quux, - val quux2: Quux?, - val corge1: JsonValueGetter, - val corge2: JsonValueGetter?, - val grault1: T, - val grault2: T? - ) + @Test + fun valueParamFieldAnnoated() { + data class Data( + val nonNull: ValueParamFieldAnnotated, + val nullable: ValueParamFieldAnnotated? + ) + + assertEquals( + """ + { + "nonNull" : 0, + "nullable" : 1 + } + """.trimIndent(), + writer.writeValueAsString( + Data( + ValueParamFieldAnnotated(0), + ValueParamFieldAnnotated(1) + ) + ) + ) + } @Test - fun inProperty() { - val writer = jacksonMapperBuilder().build().writerWithDefaultPrettyPrinter() + fun propertyWithOverriddenGetter() { + data class Data( + val nonNull: PropertyWithOverriddenGetter, + val nullable: PropertyWithOverriddenGetter? + ) assertEquals( """ { - "foo1" : 0, - "foo2" : 1, - "bar1" : 2, - "bar2" : 3, - "baz1" : "Baz(value=4)", - "baz2" : "Baz(value=5)", - "qux1" : "Qux(value=6)", - "qux2" : "Qux(value=7)", - "quux1" : "Quux(value=8)", - "quux2" : "Quux(value=9)", - "corge1" : "Corge(value=10)", - "corge2" : "Corge(value=11)", - "grault1" : "Grault(value=12)", - "grault2" : "Grault(value=13)" + "nonNull" : "PropertyWithOverriddenGetter(value=0)", + "nullable" : "PropertyWithOverriddenGetter(value=1)" } """.trimIndent(), writer.writeValueAsString( Data( - Foo(0), Foo(1), - Bar(2), Bar(3), - Baz(4), Baz(5), - Qux(6), Qux(7), - Quux(8), Quux(9), - Corge(10), Corge(11), - Grault(12), Grault(13) + PropertyWithOverriddenGetter(0), + PropertyWithOverriddenGetter(1) ) ) ) } @Test - fun inCollection() { - val writer = jacksonMapperBuilder().build().writerWithDefaultPrettyPrinter() + fun directlyOverriddenGetter() { + data class Data( + val nonNull: DirectlyOverriddenGetter, + val nullable: DirectlyOverriddenGetter? + ) assertEquals( - "[ 0, 1, \"Baz(value=2)\", \"Qux(value=3)\", \"Quux(value=4)\" ]", - writer.writeValueAsString(listOf(Foo(0), Bar(1), Baz(2), Qux(3), Quux(4))) + """ + { + "nonNull" : "DirectlyOverriddenGetter(value=0)", + "nullable" : "DirectlyOverriddenGetter(value=1)" + } + """.trimIndent(), + writer.writeValueAsString( + Data( + DirectlyOverriddenGetter(0), + DirectlyOverriddenGetter(1) + ) + ) ) } @Test - fun inArray() { - val writer = jacksonMapperBuilder().build().writerWithDefaultPrettyPrinter() + fun propertyWithOverriddenGetterAsParameterizedType() { + data class Data( + val nonNull: T, + val nullable: T? + ) assertEquals( - "[ 0, 1, \"Baz(value=2)\", \"Qux(value=3)\", \"Quux(value=4)\" ]", - writer.writeValueAsString(arrayOf(Foo(0), Bar(1), Baz(2), Qux(3), Quux(4))) + """ + { + "nonNull" : "PropertyWithOverriddenGetter(value=0)", + "nullable" : "PropertyWithOverriddenGetter(value=1)" + } + """.trimIndent(), + writer.writeValueAsString( + Data( + PropertyWithOverriddenGetter(0), + PropertyWithOverriddenGetter(1) + ) + ) + ) + } + + @Test + fun jsonValueGetterImplementationAsConcreteType() { + data class Data( + val nonNull: JsonValueGetterImplementation, + val nullable: JsonValueGetterImplementation? + ) + + assertEquals( + """ + { + "nonNull" : "JsonValueGetterImplementation(value=0)", + "nullable" : "JsonValueGetterImplementation(value=1)" + } + """.trimIndent(), + writer.writeValueAsString( + Data( + JsonValueGetterImplementation(0), + JsonValueGetterImplementation(1) + ) + ) + ) + } + + @Test + fun jsonValueGetterImplementationAsGenericType() { + data class Data( + val nonNull: JsonValueGetter, + val nullable: JsonValueGetter? + ) + + assertEquals( + """ + { + "nonNull" : "JsonValueGetterImplementation(value=0)", + "nullable" : "JsonValueGetterImplementation(value=1)" + } + """.trimIndent(), + writer.writeValueAsString( + Data( + JsonValueGetterImplementation(0), + JsonValueGetterImplementation(1) + ) + ) + ) + } + + @Test + fun inCollection() { + assertEquals( + "[ 0, 1, \"PropertyWithOverriddenGetter(value=2)\", \"DirectlyOverriddenGetter(value=3)\", \"JsonValueGetterImplementation(value=4)\" ]", + writer.writeValueAsString( + listOf( + ValueParamGetterAnnotated(0), + ValueParamFieldAnnotated(1), + PropertyWithOverriddenGetter(2), + DirectlyOverriddenGetter(3), + JsonValueGetterImplementation(4) + ) + ) + ) + } + + @Test + fun inArray() { + assertEquals( + "[ 0, 1, \"PropertyWithOverriddenGetter(value=2)\", \"DirectlyOverriddenGetter(value=3)\", \"JsonValueGetterImplementation(value=4)\" ]", + writer.writeValueAsString( + arrayOf( + ValueParamGetterAnnotated(0), + ValueParamFieldAnnotated(1), + PropertyWithOverriddenGetter(2), + DirectlyOverriddenGetter(3), + JsonValueGetterImplementation(4) + ) + ) ) } } From cc6c5368a0f5fc8a2456a997325e178b8beeb8f5 Mon Sep 17 00:00:00 2001 From: Drew Stephens Date: Mon, 3 Jan 2022 10:46:24 -0500 Subject: [PATCH 38/46] Update japicmp excludes --- pom.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pom.xml b/pom.xml index 91dbace1..dd4b2f89 100644 --- a/pom.xml +++ b/pom.xml @@ -208,6 +208,13 @@ TODO Remove after release of 2.14 and update the oldVersion above to 2.14. --> com.fasterxml.jackson.module.kotlin.ValueClassBoxSerializer + com.fasterxml.jackson.module.kotlin.ValueClassSerializer$StaticJsonValue + com.fasterxml.jackson.module.kotlin.ValueClassSerializer$Unbox + com.fasterxml.jackson.module.kotlin.ValueClassStaticJsonValueSerializer + com.fasterxml.jackson.module.kotlin.ValueClassUnboxSerializer.serialize(java.lang.Object, com.fasterxml.jackson.core.JsonGenerator, com.fasterxml.jackson.databind.SerializerProvider) + com.fasterxml.jackson.databind.jsonschema.SchemaAware[com.fasterxml.jackson.databind.jsonschema.SchemaAware] + com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitable[com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitable]:INTERFACE_REMOVED, java.io.Serializable[java.io.Serializable] + com.fasterxml.jackson.module.kotlin.ValueClassUnboxSerializer From 844ce3538f31317d7d34e15e80e719be12e814c1 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Tue, 4 Jan 2022 01:14:55 +0900 Subject: [PATCH 39/46] In order to reduce processing costs, not rewrite if not necessary. --- .../fasterxml/jackson/module/kotlin/ConstructorValueCreator.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/ConstructorValueCreator.kt b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/ConstructorValueCreator.kt index 0aaa0a75..d02c6b85 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/ConstructorValueCreator.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/ConstructorValueCreator.kt @@ -7,6 +7,6 @@ internal class ConstructorValueCreator(override val callable: KFunction) : override val accessible: Boolean = callable.isAccessible init { - callable.isAccessible = true + if (!accessible) callable.isAccessible = true } } From b01c4bdcac7a33444f67f2ca3156041891d42904 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Tue, 4 Jan 2022 01:23:42 +0900 Subject: [PATCH 40/46] Fix the call would fail if callable.isAccessible was false. --- .../fasterxml/jackson/module/kotlin/MethodValueCreator.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/MethodValueCreator.kt b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/MethodValueCreator.kt index 986ec076..15fe6267 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/MethodValueCreator.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/MethodValueCreator.kt @@ -23,11 +23,15 @@ internal class MethodValueCreator private constructor( // abort, we have some unknown case here if (!possibleCompanion.isCompanion) return null + val initialCallableAccessible = callable.isAccessible + if (!initialCallableAccessible) callable.isAccessible = true + val (companionObjectInstance: Any, accessible: Boolean) = try { // throws ex val instance = possibleCompanion.objectInstance!! + // If an instance of the companion object can be obtained, accessibility depends on the KFunction - instance to callable.isAccessible + instance to initialCallableAccessible } catch (ex: IllegalAccessException) { // fallback for when an odd access exception happens through Kotlin reflection possibleCompanion.java.enclosingClass.fields From c0a58aba50b7ed3420c42042f72ed571ecd5305d Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Tue, 4 Jan 2022 01:32:09 +0900 Subject: [PATCH 41/46] Fix the call would fail if the companion object was private. --- .../com/fasterxml/jackson/module/kotlin/MethodValueCreator.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/MethodValueCreator.kt b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/MethodValueCreator.kt index 15fe6267..c5ca99fd 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/MethodValueCreator.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/MethodValueCreator.kt @@ -34,7 +34,7 @@ internal class MethodValueCreator private constructor( instance to initialCallableAccessible } catch (ex: IllegalAccessException) { // fallback for when an odd access exception happens through Kotlin reflection - possibleCompanion.java.enclosingClass.fields + possibleCompanion.java.enclosingClass.declaredFields .firstOrNull { it.type.kotlin.isCompanion } ?.let { it.isAccessible = true From 3428cd34202e6acba09f8a28239cd7c3e60851c0 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Tue, 4 Jan 2022 01:32:19 +0900 Subject: [PATCH 42/46] Fix test case --- .../jackson/module/kotlin/test/ParameterNameTests.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/ParameterNameTests.kt b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/ParameterNameTests.kt index 64169d4f..faf739c2 100644 --- a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/ParameterNameTests.kt +++ b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/ParameterNameTests.kt @@ -199,8 +199,8 @@ class TestJacksonWithKotlin { private class StateObjectWithFactoryOnNamedCompanion private constructor (override val name: String, override val age: Int, override val primaryAddress: String, override val wrongName: Boolean, override val createdDt: Date) : TestFields { var factoryUsed: Boolean = false - companion object Named { - @JvmStatic @JsonCreator fun create(@JsonProperty("name") nameThing: String, @JsonProperty("age") age: Int, @JsonProperty("primaryAddress") primaryAddress: String, @JsonProperty("renamed") wrongName: Boolean, @JsonProperty("createdDt") createdDt: Date): StateObjectWithFactoryOnNamedCompanion { + private companion object Named { + @JvmStatic @JsonCreator private fun create(@JsonProperty("name") nameThing: String, @JsonProperty("age") age: Int, @JsonProperty("primaryAddress") primaryAddress: String, @JsonProperty("renamed") wrongName: Boolean, @JsonProperty("createdDt") createdDt: Date): StateObjectWithFactoryOnNamedCompanion { val obj = StateObjectWithFactoryOnNamedCompanion(nameThing, age, primaryAddress, wrongName, createdDt) obj.factoryUsed = true return obj From 5eca9cd70fa125a94a822ba0f7c92478512e228a Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Tue, 4 Jan 2022 21:01:48 +0900 Subject: [PATCH 43/46] add comments --- .../fasterxml/jackson/module/kotlin/ConstructorValueCreator.kt | 1 + .../com/fasterxml/jackson/module/kotlin/MethodValueCreator.kt | 1 + 2 files changed, 2 insertions(+) diff --git a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/ConstructorValueCreator.kt b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/ConstructorValueCreator.kt index d02c6b85..4f711679 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/ConstructorValueCreator.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/ConstructorValueCreator.kt @@ -7,6 +7,7 @@ internal class ConstructorValueCreator(override val callable: KFunction) : override val accessible: Boolean = callable.isAccessible init { + // To prevent the call from failing, save the initial value and then rewrite the flag. if (!accessible) callable.isAccessible = true } } diff --git a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/MethodValueCreator.kt b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/MethodValueCreator.kt index c5ca99fd..96f489a1 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/MethodValueCreator.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/MethodValueCreator.kt @@ -23,6 +23,7 @@ internal class MethodValueCreator private constructor( // abort, we have some unknown case here if (!possibleCompanion.isCompanion) return null + // To prevent the call from failing, save the initial value and then rewrite the flag. val initialCallableAccessible = callable.isAccessible if (!initialCallableAccessible) callable.isAccessible = true From b19c44f9461f4e6b4a26bc64dd8268ed6628f340 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Tue, 4 Jan 2022 21:04:35 +0900 Subject: [PATCH 44/46] formatting --- .../module/kotlin/test/ParameterNameTests.kt | 118 ++++++++++++++---- 1 file changed, 97 insertions(+), 21 deletions(-) diff --git a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/ParameterNameTests.kt b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/ParameterNameTests.kt index faf739c2..a27311c5 100644 --- a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/ParameterNameTests.kt +++ b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/ParameterNameTests.kt @@ -26,7 +26,13 @@ class TestJacksonWithKotlin { val wrongName: Boolean val createdDt: Date - fun validate(nameField: String = name, ageField: Int = age, addressField: String = primaryAddress, wrongNameField: Boolean = wrongName, createDtField: Date = createdDt) { + fun validate( + nameField: String = name, + ageField: Int = age, + addressField: String = primaryAddress, + wrongNameField: Boolean = wrongName, + createDtField: Date = createdDt + ) { assertThat(nameField, equalTo("Frank")) assertThat(ageField, equalTo(30)) assertThat(addressField, equalTo("something here")) @@ -50,7 +56,10 @@ class TestJacksonWithKotlin { // ================== - private class DefaultAndSpecificConstructor(override var name: String = "", override var age: Int = 0) : TestFields { + private class DefaultAndSpecificConstructor( + override var name: String = "", + override var age: Int = 0 + ) : TestFields { @JsonProperty("renamed") override var wrongName: Boolean = false @@ -65,7 +74,13 @@ class TestJacksonWithKotlin { // ================== - private class NoFailWithoutJsonCreator(override val name: String, override val age: Int, override val primaryAddress: String, val renamed: Boolean, override val createdDt: Date) : TestFields { + private class NoFailWithoutJsonCreator( + override val name: String, + override val age: Int, + override val primaryAddress: String, + val renamed: Boolean, + override val createdDt: Date + ) : TestFields { @JsonIgnore override val wrongName = renamed // here for the test validation only } @@ -77,7 +92,13 @@ class TestJacksonWithKotlin { // ================== - private data class StateObjectAsDataClassExplicitJsonCreator @JsonCreator constructor(override val name: String, override val age: Int, override val primaryAddress: String, val renamed: Boolean, override val createdDt: Date) : TestFields { + private data class StateObjectAsDataClassExplicitJsonCreator @JsonCreator constructor( + override val name: String, + override val age: Int, + override val primaryAddress: String, + val renamed: Boolean, + override val createdDt: Date + ) : TestFields { @JsonIgnore override val wrongName = renamed // here for the test validation only } @@ -95,11 +116,18 @@ class TestJacksonWithKotlin { // ================== - private data class StateObjectAsDataClassWithJsonCreatorAndJsonProperty @JsonCreator constructor(override val name: String, override val age: Int, override val primaryAddress: String, @JsonProperty("renamed") override val wrongName: Boolean, override val createdDt: Date) : TestFields + private data class StateObjectAsDataClassWithJsonCreatorAndJsonProperty @JsonCreator constructor( + override val name: String, + override val age: Int, + override val primaryAddress: String, + @JsonProperty("renamed") override val wrongName: Boolean, + override val createdDt: Date + ) : TestFields @Test fun testDataClassWithExplicitJsonCreatorAndJsonProperty() { // data class with JsonCreator and JsonProperty - val stateObj = normalCasedMapper.readValue(normalCasedJson) + val stateObj = normalCasedMapper + .readValue(normalCasedJson) stateObj.validate() val test1out = normalCasedMapper.writeValueAsString(stateObj) @@ -108,7 +136,13 @@ class TestJacksonWithKotlin { // ================== - private class StateObjectAsNormalClass @JsonCreator constructor(override val name: String, override val age: Int, override val primaryAddress: String, @JsonProperty("renamed") override val wrongName: Boolean, override val createdDt: Date) : TestFields + private class StateObjectAsNormalClass @JsonCreator constructor( + override val name: String, + override val age: Int, + override val primaryAddress: String, + @JsonProperty("renamed") override val wrongName: Boolean, + override val createdDt: Date + ) : TestFields @Test fun testNormalClassWithJsonCreator() { // normal class @@ -118,7 +152,11 @@ class TestJacksonWithKotlin { // ================== - private class StateObjectWithPartialFieldsInConstructor(override val name: String, override val age: Int, override val primaryAddress: String) : TestFields { + private class StateObjectWithPartialFieldsInConstructor( + override val name: String, + override val age: Int, + override val primaryAddress: String + ) : TestFields { @JsonProperty("renamed") override var wrongName: Boolean = false override var createdDt: Date by Delegates.notNull() } @@ -131,13 +169,15 @@ class TestJacksonWithKotlin { // ================== - private class StateObjectAsDataClassConfusingConstructor constructor (@Suppress("UNUSED_PARAMETER") nonField: String?, - override val name: String, - @Suppress("UNUSED_PARAMETER") yearOfBirth: Int, - override val age: Int, - override val primaryAddress: String, - @JsonProperty("renamed") override val wrongName: Boolean, - override val createdDt: Date) : TestFields + private class StateObjectAsDataClassConfusingConstructor constructor( + @Suppress("UNUSED_PARAMETER") nonField: String?, + override val name: String, + @Suppress("UNUSED_PARAMETER") yearOfBirth: Int, + override val age: Int, + override val primaryAddress: String, + @JsonProperty("renamed") override val wrongName: Boolean, + override val createdDt: Date + ) : TestFields @Test fun testDataClassWithNonFieldParametersInConstructor() { // data class with non fields appearing as parameters in constructor, this works but null values or defaults for primitive types are passed to @@ -162,10 +202,22 @@ class TestJacksonWithKotlin { // ================== - private class StateObjectWithFactory private constructor (override val name: String, override val age: Int, override val primaryAddress: String, override val wrongName: Boolean, override val createdDt: Date) : TestFields { + private class StateObjectWithFactory private constructor( + override val name: String, + override val age: Int, + override val primaryAddress: String, + override val wrongName: Boolean, + override val createdDt: Date + ) : TestFields { var factoryUsed: Boolean = false companion object { - @JvmStatic @JsonCreator fun create(@JsonProperty("name") nameThing: String, @JsonProperty("age") age: Int, @JsonProperty("primaryAddress") primaryAddress: String, @JsonProperty("renamed") wrongName: Boolean, @JsonProperty("createdDt") createdDt: Date): StateObjectWithFactory { + @JvmStatic @JsonCreator fun create( + @JsonProperty("name") nameThing: String, + @JsonProperty("age") age: Int, + @JsonProperty("primaryAddress") primaryAddress: String, + @JsonProperty("renamed") wrongName: Boolean, + @JsonProperty("createdDt") createdDt: Date + ): StateObjectWithFactory { val obj = StateObjectWithFactory(nameThing, age, primaryAddress, wrongName, createdDt) obj.factoryUsed = true return obj @@ -179,9 +231,21 @@ class TestJacksonWithKotlin { assertThat(stateObj.factoryUsed, equalTo(true)) } - private class StateObjectWithFactoryNoParamAnnotations(val name: String, val age: Int, val primaryAddress: String, val renamed: Boolean, val createdDt: Date) { + private class StateObjectWithFactoryNoParamAnnotations( + val name: String, + val age: Int, + val primaryAddress: String, + val renamed: Boolean, + val createdDt: Date + ) { companion object { - @JvmStatic @JsonCreator fun create(name: String, age: Int, primaryAddress: String, renamed: Boolean, createdDt: Date): StateObjectWithFactoryNoParamAnnotations { + @JvmStatic @JsonCreator fun create( + name: String, + age: Int, + primaryAddress: String, + renamed: Boolean, + createdDt: Date + ): StateObjectWithFactoryNoParamAnnotations { return StateObjectWithFactoryNoParamAnnotations(name, age, primaryAddress, renamed, createdDt) } } @@ -197,10 +261,22 @@ class TestJacksonWithKotlin { } } - private class StateObjectWithFactoryOnNamedCompanion private constructor (override val name: String, override val age: Int, override val primaryAddress: String, override val wrongName: Boolean, override val createdDt: Date) : TestFields { + private class StateObjectWithFactoryOnNamedCompanion private constructor( + override val name: String, + override val age: Int, + override val primaryAddress: String, + override val wrongName: Boolean, + override val createdDt: Date + ) : TestFields { var factoryUsed: Boolean = false private companion object Named { - @JvmStatic @JsonCreator private fun create(@JsonProperty("name") nameThing: String, @JsonProperty("age") age: Int, @JsonProperty("primaryAddress") primaryAddress: String, @JsonProperty("renamed") wrongName: Boolean, @JsonProperty("createdDt") createdDt: Date): StateObjectWithFactoryOnNamedCompanion { + @JvmStatic @JsonCreator private fun create( + @JsonProperty("name") nameThing: String, + @JsonProperty("age") age: Int, + @JsonProperty("primaryAddress") primaryAddress: String, + @JsonProperty("renamed") wrongName: Boolean, + @JsonProperty("createdDt") createdDt: Date + ): StateObjectWithFactoryOnNamedCompanion { val obj = StateObjectWithFactoryOnNamedCompanion(nameThing, age, primaryAddress, wrongName, createdDt) obj.factoryUsed = true return obj From 78bd9d42e8fb41d072f42ffd612964165316da3a Mon Sep 17 00:00:00 2001 From: Drew Stephens Date: Tue, 4 Jan 2022 16:13:31 -0500 Subject: [PATCH 45/46] Explicit imports --- .../module/kotlin/KotlinNamesAnnotationIntrospector.kt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) 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 fccf8eb3..0f2e82ee 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinNamesAnnotationIntrospector.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinNamesAnnotationIntrospector.kt @@ -14,11 +14,15 @@ import com.fasterxml.jackson.databind.introspect.NopAnnotationIntrospector import com.fasterxml.jackson.databind.util.BeanUtil import java.lang.reflect.Constructor import java.lang.reflect.Method -import java.util.* +import java.util.Locale import kotlin.reflect.KClass import kotlin.reflect.KFunction import kotlin.reflect.KParameter -import kotlin.reflect.full.* +import kotlin.reflect.full.companionObject +import kotlin.reflect.full.declaredFunctions +import kotlin.reflect.full.hasAnnotation +import kotlin.reflect.full.memberProperties +import kotlin.reflect.full.primaryConstructor import kotlin.reflect.jvm.internal.KotlinReflectionInternalError import kotlin.reflect.jvm.javaType import kotlin.reflect.jvm.kotlinFunction From 9aa88e51236f115148128b22c934c62398e23a4b Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Fri, 7 Jan 2022 20:39:39 +0900 Subject: [PATCH 46/46] Fixed to public A `private` and `JvmStatic` function was broken in `Kotlin 1.5.x`. See GitHub Comment below for details. https://github.com/FasterXML/jackson-module-kotlin/pull/533#issuecomment-1007337661 --- .../fasterxml/jackson/module/kotlin/test/ParameterNameTests.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/ParameterNameTests.kt b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/ParameterNameTests.kt index a27311c5..b3dd29a5 100644 --- a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/ParameterNameTests.kt +++ b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/ParameterNameTests.kt @@ -270,7 +270,7 @@ class TestJacksonWithKotlin { ) : TestFields { var factoryUsed: Boolean = false private companion object Named { - @JvmStatic @JsonCreator private fun create( + @JvmStatic @JsonCreator fun create( @JsonProperty("name") nameThing: String, @JsonProperty("age") age: Int, @JsonProperty("primaryAddress") primaryAddress: String,