Skip to content
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

Serialization of is-getter with continuous capital letters #545

Closed
thecodinganalyst opened this issue Jan 18, 2022 · 2 comments
Closed

Serialization of is-getter with continuous capital letters #545

thecodinganalyst opened this issue Jan 18, 2022 · 2 comments
Labels

Comments

@thecodinganalyst
Copy link

Describe the bug
During the serialization of a is-getter property with continuous capital letters (e.g. isUSDListing), the serialized property become
usdListing" instead of "isUSDListing"

To Reproduce

data class SampleWithIsGetter (
    val isUSDListing: Boolean
)

fun main() {
    runFailed()

    runWorkaround()
}

fun runFailed(){
    val sample = SampleWithIsGetter(true)
    val json = jacksonObjectMapper().writeValueAsString(sample)
    println(
        """
            Example to show that jackson serialization will fail when both conditions are fulfilled
            1. attribute name starts with a "is"
            2. attribute name after the "is" has continuous capital letters
            The serialized json shows attribute name as "usdListing" instead of "isUSDListing"
        """.trimIndent()
    )
    println(json)
    assertThrows<MismatchedInputException> { jacksonObjectMapper().readValue(json, SampleWithIsGetter::class.java) }
}


fun runWorkaround(){
    val sample = SampleWithIsGetter(true)
    val objectMapper = jacksonMapperBuilder().configure(MapperFeature.USE_STD_BEAN_NAMING, true).build()
    val json = objectMapper.writeValueAsString(sample)
    val deserialized = objectMapper.readValue(json, SampleWithIsGetter::class.java)

    println(
        """
            
            Workaround solution is to set the MapperFeature - USE_STD_BEAN_NAMING to true
            USE_STD_BEAN_NAMING will ensure attribute naming will ensure attribute name will not
            be lower-cased if it is followed by upper-cased letters.
            Meaning, getURL() will return "URL" instead of "url"
            USE_STD_BEAN_NAMING is false by default for backward compatibility
        """.trimIndent()
    )

    println(json)
    assertThat(sample, equalTo(deserialized))
}

Expected behavior

fun expectedBehavior(){
    val sample = SampleWithIsGetter(true)
    val json = jacksonObjectMapper().writeValueAsString(sample)
    val deserialized = objectMapper.readValue(json, SampleWithIsGetter::class.java)
    assertThat(sample, equalTo(deserialized))
}

Versions
Kotlin: 1.6.10
Jackson-module-kotlin: 2.13.1
Jackson-databind: 2.13.1

Additional context
Add any other context about the problem here.

@thecodinganalyst
Copy link
Author

In POJOPropertiesCollector#collectAll in jackson-databind, it first gets all the properties in the class with _addFields(), then it gets all the methods in the class with _addMethods(). In both the _addFields() and _addMethods(), if the field or method starts with "is", it will try to get a rename of the property by removing the "is". So if the field is "isUSD", it will return either "usd" or "USD", depending if the MapperFeature.USE_STD_BEAN_NAMING is true. Then in either the _addGetterMethod() or _addSetterMethod() called from the _addMethods(), it will map the method to the field. In order to map the getter/setter method correctly, the rename method called by both _addMethods() and _addFields() should be the same.

However, the rename method in _addFields() is calling KotlinNamesAnnotationIntrospector#findRenameByField() in jackson-module-kotlin, which in turn calls BeanUtil.stdManglePropertyName(), but _addMethods calls DefaultAccessorNamingStrategy#findNameForIsGetter() in jackson-databind. The root cause of this issue is that DefaultAccessorNamingStrategy#findNameForIsGetter() will consider the MapperFeature.USE_STD_BEAN_NAMING to decide whether stdManglePropertyName or legacyManglePropertyName should be used, but KotlinNamesAnnotationIntrospector#findRenameByField() will just use stdManglePropertyName directly.

Should the KotlinNamesAnnotationIntrospector use DefaultAccessorNamingStrategy#findNameForIsGetter() instead? Since the BeanUtil has a deprecated okNameForIsGetter() which states that it is replaced by the AccesorNamingStrategy.

@k163377
Copy link
Contributor

k163377 commented Mar 17, 2023

I verified on branch 2.15 and the submitted test case seems to be fixed.
This is probably a duplicate of #340, which appears to have been fixed in #641.

Therefore, this issue is closed.

@k163377 k163377 closed this as completed Mar 17, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants