Skip to content
Closed
Show file tree
Hide file tree
Changes from 7 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,33 @@ 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.declaringClass.kotlin.declaredMemberProperties
.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 +165,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-`))
}
}