Skip to content

Commit

Permalink
Merge pull request #641 from k163377/fix-#340
Browse files Browse the repository at this point in the history
Fix #340
  • Loading branch information
k163377 authored Mar 15, 2023
2 parents cf2b646 + 8ada33f commit a3376b2
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 52 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,34 +29,34 @@ 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? = when (member) {
is AnnotatedMethod -> if (member.name.contains('-') && member.parameterCount == 0) {
when {
member.name.startsWith("get") -> member.name.substringAfter("get")
member.name.startsWith("is") -> member.name.substringAfter("is")
else -> null
}?.replaceFirstChar { it.lowercase(Locale.getDefault()) }?.substringBefore('-')
} else null
is AnnotatedParameter -> findKotlinParameterName(member)
else -> null
}

// 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
// (see https://kotlinlang.org/docs/reference/java-to-kotlin-interop.html and
// https://github.com/FasterXML/jackson-databind/issues/2527
// for details)
override fun findRenameByField(config: MapperConfig<*>,
field: AnnotatedField,
implName: PropertyName): PropertyName? {
val origSimple = implName.simpleName
if (field.declaringClass.isKotlinClass() && origSimple.startsWith("is")) {
val mangledName: String? = BeanUtil.stdManglePropertyName(origSimple, 2)
if ((mangledName != null) && !mangledName.equals(origSimple)) {
return PropertyName.construct(mangledName)
}
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
}
} else null
is AnnotatedParameter -> findKotlinParameterName(member)
else -> null
}
return null
}

@Suppress("UNCHECKED_CAST")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,24 +25,27 @@ class TestJacksonWithKotlin {
val primaryAddress: String
val wrongName: Boolean
val createdDt: Date
val isName: Boolean

fun validate(
nameField: String = name,
ageField: Int = age,
addressField: String = primaryAddress,
wrongNameField: Boolean = wrongName,
createDtField: Date = createdDt
createDtField: Date = createdDt,
isNameField: Boolean = isName,
) {
assertThat(nameField, equalTo("Frank"))
assertThat(ageField, equalTo(30))
assertThat(addressField, equalTo("something here"))
assertThat(wrongNameField, equalTo(true))
assertThat(createDtField, equalTo(Date(1477419948000)))
assertThat(isNameField, equalTo(false))
}
}

private val normalCasedJson = """{"name":"Frank","age":30,"primaryAddress":"something here","renamed":true,"createdDt":"2016-10-25T18:25:48.000+00:00"}"""
private val pascalCasedJson = """{"Name":"Frank","Age":30,"PrimaryAddress":"something here","Renamed":true,"CreatedDt":"2016-10-25T18:25:48.000+00:00"}"""
private val normalCasedJson = """{"name":"Frank","age":30,"primaryAddress":"something here","renamed":true,"createdDt":"2016-10-25T18:25:48.000+00:00","isName":false}"""
private val pascalCasedJson = """{"Name":"Frank","Age":30,"PrimaryAddress":"something here","Renamed":true,"CreatedDt":"2016-10-25T18:25:48.000+00:00","IsName":false}"""

private val normalCasedMapper = jacksonObjectMapper()
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
Expand All @@ -65,6 +68,7 @@ class TestJacksonWithKotlin {

override var primaryAddress: String = ""
override var createdDt: Date = Date()
override val isName: Boolean = false
}

@Test fun NoFailWithDefaultAndSpecificConstructor() {
Expand All @@ -79,7 +83,8 @@ class TestJacksonWithKotlin {
override val age: Int,
override val primaryAddress: String,
val renamed: Boolean,
override val createdDt: Date
override val createdDt: Date,
override val isName: Boolean
) : TestFields {
@JsonIgnore
override val wrongName = renamed // here for the test validation only
Expand All @@ -97,7 +102,8 @@ class TestJacksonWithKotlin {
override val age: Int,
override val primaryAddress: String,
val renamed: Boolean,
override val createdDt: Date
override val createdDt: Date,
override val isName: Boolean
) : TestFields {
@JsonIgnore
override val wrongName = renamed // here for the test validation only
Expand All @@ -121,7 +127,8 @@ class TestJacksonWithKotlin {
override val age: Int,
override val primaryAddress: String,
@JsonProperty("renamed") override val wrongName: Boolean,
override val createdDt: Date
override val createdDt: Date,
override val isName: Boolean
) : TestFields

@Test fun testDataClassWithExplicitJsonCreatorAndJsonProperty() {
Expand All @@ -141,7 +148,8 @@ class TestJacksonWithKotlin {
override val age: Int,
override val primaryAddress: String,
@JsonProperty("renamed") override val wrongName: Boolean,
override val createdDt: Date
override val createdDt: Date,
override val isName: Boolean
) : TestFields

@Test fun testNormalClassWithJsonCreator() {
Expand All @@ -155,7 +163,8 @@ class TestJacksonWithKotlin {
private class StateObjectWithPartialFieldsInConstructor(
override val name: String,
override val age: Int,
override val primaryAddress: String
override val primaryAddress: String,
override val isName: Boolean
) : TestFields {
@JsonProperty("renamed") override var wrongName: Boolean = false
override var createdDt: Date by Delegates.notNull()
Expand All @@ -176,7 +185,8 @@ class TestJacksonWithKotlin {
override val age: Int,
override val primaryAddress: String,
@JsonProperty("renamed") override val wrongName: Boolean,
override val createdDt: Date
override val createdDt: Date,
override val isName: Boolean
) : TestFields

@Test fun testDataClassWithNonFieldParametersInConstructor() {
Expand Down Expand Up @@ -207,7 +217,8 @@ class TestJacksonWithKotlin {
override val age: Int,
override val primaryAddress: String,
override val wrongName: Boolean,
override val createdDt: Date
override val createdDt: Date,
override val isName: Boolean
) : TestFields {
var factoryUsed: Boolean = false
companion object {
Expand All @@ -216,9 +227,10 @@ class TestJacksonWithKotlin {
@JsonProperty("age") age: Int,
@JsonProperty("primaryAddress") primaryAddress: String,
@JsonProperty("renamed") wrongName: Boolean,
@JsonProperty("createdDt") createdDt: Date
@JsonProperty("createdDt") createdDt: Date,
@JsonProperty("isName") isName: Boolean
): StateObjectWithFactory {
val obj = StateObjectWithFactory(nameThing, age, primaryAddress, wrongName, createdDt)
val obj = StateObjectWithFactory(nameThing, age, primaryAddress, wrongName, createdDt, isName)
obj.factoryUsed = true
return obj
}
Expand All @@ -236,17 +248,19 @@ class TestJacksonWithKotlin {
val age: Int,
val primaryAddress: String,
val renamed: Boolean,
val createdDt: Date
val createdDt: Date,
val isName: Boolean
) {
companion object {
@JvmStatic @JsonCreator fun create(
name: String,
age: Int,
primaryAddress: String,
renamed: Boolean,
createdDt: Date
createdDt: Date,
isName: Boolean
): StateObjectWithFactoryNoParamAnnotations {
return StateObjectWithFactoryNoParamAnnotations(name, age, primaryAddress, renamed, createdDt)
return StateObjectWithFactoryNoParamAnnotations(name, age, primaryAddress, renamed, createdDt, isName)
}
}
}
Expand All @@ -266,7 +280,8 @@ class TestJacksonWithKotlin {
override val age: Int,
override val primaryAddress: String,
override val wrongName: Boolean,
override val createdDt: Date
override val createdDt: Date,
override val isName: Boolean
) : TestFields {
var factoryUsed: Boolean = false
private companion object Named {
Expand All @@ -275,9 +290,10 @@ class TestJacksonWithKotlin {
@JsonProperty("age") age: Int,
@JsonProperty("primaryAddress") primaryAddress: String,
@JsonProperty("renamed") wrongName: Boolean,
@JsonProperty("createdDt") createdDt: Date
@JsonProperty("createdDt") createdDt: Date,
@JsonProperty("isName") isName: Boolean
): StateObjectWithFactoryOnNamedCompanion {
val obj = StateObjectWithFactoryOnNamedCompanion(nameThing, age, primaryAddress, wrongName, createdDt)
val obj = StateObjectWithFactoryOnNamedCompanion(nameThing, age, primaryAddress, wrongName, createdDt, isName)
obj.factoryUsed = true
return obj
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
package com.fasterxml.jackson.module.kotlin.test.github.failing
package com.fasterxml.jackson.module.kotlin.test.github

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.kotlinModule
import com.fasterxml.jackson.module.kotlin.readValue
import com.fasterxml.jackson.module.kotlin.test.expectFailure
import org.junit.Test
import kotlin.test.assertEquals

Expand All @@ -21,15 +20,27 @@ class OwnerRequestTest {

@Test
fun testDeserHit340() {
expectFailure<UnrecognizedPropertyException>("GitHub #340 has been fixed!") {
val value: IsField = jackson.readValue(json)
assertEquals("Got a foo", value.foo)
}
val value: IsField = jackson.readValue(json)
// Fixed
assertEquals("Got a foo", value.foo)
}

@Test
fun testDeserWithoutIssue() {
val value: NoIsField = jackson.readValue(json)
assertEquals("Got a foo", value.foo)
}

// A test case for isSetter to work, added with the fix for this issue.
class IsSetter {
lateinit var isFoo: String
}

@Test
fun isSetterTest() {
val json = """{"isFoo":"bar"}"""
val isSetter: IsSetter = jackson.readValue(json)

assertEquals("bar", isSetter.isFoo)
}
}

0 comments on commit a3376b2

Please sign in to comment.