diff --git a/pom.xml b/pom.xml index 5a1635020..b63cc7501 100644 --- a/pom.xml +++ b/pom.xml @@ -221,6 +221,11 @@ com.fasterxml.jackson.module.kotlin.ULongSerializer com.fasterxml.jackson.module.kotlin.UShortDeserializer com.fasterxml.jackson.module.kotlin.UShortSerializer + + com.fasterxml.jackson.module.kotlin.KotlinNamesAnnotationIntrospectorKt 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 e71d5e099..93d09f846 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinNamesAnnotationIntrospector.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinNamesAnnotationIntrospector.kt @@ -18,37 +18,41 @@ import java.util.Locale 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.javaGetter import kotlin.reflect.jvm.javaType import kotlin.reflect.jvm.kotlinFunction internal class KotlinNamesAnnotationIntrospector(val module: KotlinModule, val cache: ReflectionCache, val ignoredClassesForImplyingJsonCreator: Set>) : NopAnnotationIntrospector() { // since 2.4 - override fun findImplicitPropertyName(member: AnnotatedMember): String? { - if (member is AnnotatedMethod && member.isInlineClass()) { - if (member.name.startsWith("get") && - member.name.contains('-') && - member.parameterCount == 0) { - return member.name.substringAfter("get") - .replaceFirstChar { it.lowercase(Locale.getDefault()) } - .substringBefore('-') - } else if (member.name.startsWith("is") && - member.name.contains('-') && - member.parameterCount == 0) { - return member.name.substringAfter("is") - .replaceFirstChar { it.lowercase(Locale.getDefault()) } - .substringBefore('-') - } - } else if (member is AnnotatedParameter) { - return findKotlinParameterName(member) + override fun findImplicitPropertyName(member: AnnotatedMember): String? = when (member) { + is AnnotatedMethod -> findImplicitPropertyNameFromKotlinPropertyIfNeeded(member) + is AnnotatedParameter -> findKotlinParameterName(member) + else -> null + } + + // Use Kotlin property names as needed. + private fun findImplicitPropertyNameFromKotlinPropertyIfNeeded(member: AnnotatedMethod): String? = member + .takeIf { + it.parameterCount == 0 && looksLikeKotlinGeneratedMethod(it.name) && !it.hasAnnotation(JvmName::class.java) + }?.let { _ -> + member.member.kotlinFunction + ?.let { getterLike -> + // for getter like method + val name = getterLike.name + val replaceTarget = if (name.startsWith("get")) "get" else "is" + + name.substringAfter(replaceTarget).replaceFirstChar { it.lowercase(Locale.getDefault()) } + } + ?: member.declaringClass.kotlin.declaredMemberProperties // for property getter + .find { kProperty -> kProperty.javaGetter == member.member } + ?.name } - return null - } + // Since getter for value class (inline class) will be compiled into a different name such as "getFoo-${random}". + private fun looksLikeKotlinGeneratedMethod(methodName: String) = + methodName.run { contains("-") && (startsWith("get") || startsWith("is")) } // since 2.11: support Kotlin's way of handling "isXxx" backed properties where // logical property name needs to remain "isXxx" and not become "xxx" as with Java Beans @@ -169,5 +173,3 @@ internal class KotlinNamesAnnotationIntrospector(val module: KotlinModule, val c } } } - -private fun AnnotatedMethod.isInlineClass() = declaringClass.declaredMethods.any { it.name.contains('-') } diff --git a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/github/Github356.kt b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/github/Github356.kt index d77072e89..821081825 100644 --- a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/github/Github356.kt +++ b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/github/Github356.kt @@ -21,6 +21,30 @@ class TestGithub356 { assertEquals("""{"inlineClassProperty":"bar"}""", mapper.writeValueAsString(original)) } + @Test + fun deserializeKebabInlineMember() { + val original = ClassWithKebabInlineMember(ValueClass("bar")) + assertEquals(original, mapper.readValue(mapper.writeValueAsString(original))) + } + + @Test + fun serializeKebabInlineClass() { + val original = ClassWithKebabInlineMember(ValueClass("bar")) + assertEquals("""{"value-class-property":"bar"}""", mapper.writeValueAsString(original)) + } + + @Test + fun deserializeNamedInlineClass() { + val original = ClassWithNamedInlineMember(ValueClass("bar")) + assertEquals(original, mapper.readValue(mapper.writeValueAsString(original))) + } + + @Test + fun serializeNamedInlineClass() { + val original = ClassWithNamedInlineMember(ValueClass("bar")) + assertEquals("""{"value-":"bar"}""", mapper.writeValueAsString(original)) + } + @Test fun deserializeValueClass() { val original = ClassWithValueMember(ValueClass("bar")) @@ -54,3 +78,17 @@ data class ClassWithValueMember(val valueClassProperty: ValueClass) { fun build() = ClassWithValueMember(ValueClass(valueClassProperty)) } } + +@JsonDeserialize(builder = ClassWithKebabInlineMember.JacksonBuilder::class) +data class ClassWithKebabInlineMember(val `value-class-property`: ValueClass) { + data class JacksonBuilder constructor(val `value-class-property`: String) { + fun build() = ClassWithKebabInlineMember(ValueClass(`value-class-property`)) + } +} + +@JsonDeserialize(builder = ClassWithNamedInlineMember.JacksonBuilder::class) +data class ClassWithNamedInlineMember(@get:JvmName("getValue-") val `value-`: ValueClass) { + data class JacksonBuilder constructor(val `value-`: String) { + fun build() = ClassWithNamedInlineMember(ValueClass(`value-`)) + } +}