Skip to content

Commit

Permalink
Merge pull request #686 from k163377/feat/kotlin-name
Browse files Browse the repository at this point in the history
Add UseKotlinPropertyNameForGetter option
  • Loading branch information
k163377 authored Jul 28, 2023
2 parents 0860546 + aa7217f commit b523bb2
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 25 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.fasterxml.jackson.module.kotlin

import java.util.BitSet
import kotlin.math.pow

/**
* @see KotlinModule.Builder
Expand Down Expand Up @@ -42,7 +41,24 @@ enum class KotlinFeature(private val enabledByDefault: Boolean) {
* may contain null values after deserialization.
* Enabling it protects against this but has significant performance impact.
*/
StrictNullChecks(enabledByDefault = false);
StrictNullChecks(enabledByDefault = false),

/**
* By enabling this feature, the property name on Kotlin is used as the implicit name for the getter.
*
* By default, the getter name is used during serialization.
* This name may be different from the parameter/field name, in which case serialization results
* may be incorrect or annotations may malfunction.
* See [jackson-module-kotlin#630] for details.
*
* By enabling this feature, such malfunctions will not occur.
*
* On the other hand, enabling this option increases the amount of reflection processing,
* which may result in performance degradation for both serialization and deserialization.
* In addition, the adjustment of behavior using get:JvmName is disabled.
* Note also that this feature does not apply to setters.
*/
KotlinPropertyNameAsImplicitName(enabledByDefault = false);

internal val bitSet: BitSet = (1 shl ordinal).toBitSet()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import com.fasterxml.jackson.module.kotlin.KotlinFeature.NullIsSameAsDefault
import com.fasterxml.jackson.module.kotlin.KotlinFeature.NullToEmptyCollection
import com.fasterxml.jackson.module.kotlin.KotlinFeature.NullToEmptyMap
import com.fasterxml.jackson.module.kotlin.KotlinFeature.StrictNullChecks
import com.fasterxml.jackson.module.kotlin.KotlinFeature.KotlinPropertyNameAsImplicitName
import com.fasterxml.jackson.module.kotlin.SingletonSupport.CANONICALIZE
import com.fasterxml.jackson.module.kotlin.SingletonSupport.DISABLED
import java.util.*
Expand Down Expand Up @@ -53,7 +54,8 @@ class KotlinModule @Deprecated(
val nullToEmptyMap: Boolean = false,
val nullIsSameAsDefault: Boolean = false,
val singletonSupport: SingletonSupport = DISABLED,
val strictNullChecks: Boolean = false
val strictNullChecks: Boolean = false,
val useKotlinPropertyNameForGetter: Boolean = false
) : SimpleModule(KotlinModule::class.java.name, PackageVersion.VERSION) {
init {
if (!KotlinVersion.CURRENT.isAtLeast(1, 5)) {
Expand Down Expand Up @@ -102,7 +104,8 @@ class KotlinModule @Deprecated(
builder.isEnabled(KotlinFeature.SingletonSupport) -> CANONICALIZE
else -> DISABLED
},
builder.isEnabled(StrictNullChecks)
builder.isEnabled(StrictNullChecks),
builder.isEnabled(KotlinPropertyNameAsImplicitName)
)

companion object {
Expand Down Expand Up @@ -130,7 +133,13 @@ class KotlinModule @Deprecated(
}

context.insertAnnotationIntrospector(KotlinAnnotationIntrospector(context, cache, nullToEmptyCollection, nullToEmptyMap, nullIsSameAsDefault))
context.appendAnnotationIntrospector(KotlinNamesAnnotationIntrospector(this, cache, ignoredClassesForImplyingJsonCreator))
context.appendAnnotationIntrospector(
KotlinNamesAnnotationIntrospector(
this,
cache,
ignoredClassesForImplyingJsonCreator,
useKotlinPropertyNameForGetter)
)

context.addDeserializers(KotlinDeserializers())
context.addKeyDeserializers(KotlinKeyDeserializers)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,35 +24,57 @@ 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.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() {
internal class KotlinNamesAnnotationIntrospector(
val module: KotlinModule,
val cache: ReflectionCache,
val ignoredClassesForImplyingJsonCreator: Set<KClass<*>>,
val useKotlinPropertyNameForGetter: Boolean
) : NopAnnotationIntrospector() {
private fun getterNameFromJava(member: AnnotatedMethod): String? {
val name = member.name

// The reason for truncating after `-` is to truncate the random suffix
// given after the value class accessor name.
return when {
name.startsWith("get") -> name.takeIf { it.contains("-") }?.let { _ ->
name.substringAfter("get")
.replaceFirstChar { it.lowercase(Locale.getDefault()) }
.substringBefore('-')
}
// since 2.15: 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
// (see https://kotlinlang.org/docs/reference/java-to-kotlin-interop.html and
// https://github.com/FasterXML/jackson-databind/issues/2527 and
// https://github.com/FasterXML/jackson-module-kotlin/issues/340
// for details)
name.startsWith("is") -> if (name.contains("-")) name.substringAfter("-") else name
else -> null
}
}

private fun getterNameFromKotlin(member: AnnotatedMethod): String? {
val getter = member.member

return member.member.declaringClass.takeIf { it.isKotlinClass() }?.let { clazz ->
clazz.kotlin.memberProperties.find { it.javaGetter == getter }
?.let { it.name }
}
}

// since 2.4
override fun findImplicitPropertyName(member: AnnotatedMember): String? {
if (!member.declaringClass.isKotlinClass()) return null

val name = member.name

return when (member) {
is AnnotatedMethod -> if (member.parameterCount == 0) {
// The reason for truncating after `-` is to truncate the random suffix
// given after the value class accessor name.
when {
name.startsWith("get") -> name.takeIf { it.contains("-") }?.let { _ ->
name.substringAfter("get")
.replaceFirstChar { it.lowercase(Locale.getDefault()) }
.substringBefore('-')
}
// since 2.15: 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
// (see https://kotlinlang.org/docs/reference/java-to-kotlin-interop.html and
// https://github.com/FasterXML/jackson-databind/issues/2527 and
// https://github.com/FasterXML/jackson-module-kotlin/issues/340
// for details)
name.startsWith("is") -> if (name.contains("-")) name.substringAfter("-") else name
else -> null
}
if (useKotlinPropertyNameForGetter) {
// Fall back to default if it is a getter-like function
getterNameFromKotlin(member) ?: getterNameFromJava(member)
} else getterNameFromJava(member)
} else null
is AnnotatedParameter -> findKotlinParameterName(member)
else -> null
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.fasterxml.jackson.module.kotlin.test.github

import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.KotlinFeature
import com.fasterxml.jackson.module.kotlin.KotlinModule
import org.junit.Test
import kotlin.test.assertEquals

class Github630 {
private val mapper = ObjectMapper()
.registerModule(KotlinModule.Builder().enable(KotlinFeature.KotlinPropertyNameAsImplicitName).build())!!

data class Dto(
// from #570, #603
val FOO: Int = 0,
val bAr: Int = 0,
@JsonProperty("b")
val BAZ: Int = 0,
@JsonProperty("q")
val qUx: Int = 0,
// from #71
internal val quux: Int = 0,
// from #434
val `corge-corge`: Int = 0,
// additional
@get:JvmName("aaa")
val grault: Int = 0
)

@Test
fun test() {
val dto = Dto()

assertEquals(
"""{"FOO":0,"bAr":0,"b":0,"q":0,"quux":0,"corge-corge":0,"grault":0}""",
mapper.writeValueAsString(dto)
)
}
}

0 comments on commit b523bb2

Please sign in to comment.