Skip to content

Fixed serialization/deserialization failure when the name of getter contains -. #451

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,11 @@
<exclude>com.fasterxml.jackson.module.kotlin.ULongSerializer</exclude>
<exclude>com.fasterxml.jackson.module.kotlin.UShortDeserializer</exclude>
<exclude>com.fasterxml.jackson.module.kotlin.UShortSerializer</exclude>
<!--
#451 private top-level function removed which caused this class to disappear,
though to no effect.
-->
<exclude>com.fasterxml.jackson.module.kotlin.KotlinNamesAnnotationIntrospectorKt</exclude>
</excludes>
</parameter>
</configuration>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<KClass<*>>) : 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
Expand Down Expand Up @@ -169,5 +173,3 @@ internal class KotlinNamesAnnotationIntrospector(val module: KotlinModule, val c
}
}
}

private fun AnnotatedMethod.isInlineClass() = declaringClass.declaredMethods.any { it.name.contains('-') }
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
Expand Down Expand Up @@ -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-`))
}
}