jackson-module-kotlin icon indicating copy to clipboard operation
jackson-module-kotlin copied to clipboard

ObjectMapper serialization for boolean fields named isFieldName changed between versions 2.10.5 and 2.12.4

Open kyleboon opened this issue 3 years ago • 5 comments

(NOTE: transferred from "jackson-databind", assumed Kotlin-module specific)

Describe the bug I'm upgrading a project from Jackson 2.10.5 to 2.12.4 and one of the classes started serializing differently. The class has a field named isOpen which is not a boolean. In 2.10.5 this serialized as "isOpen": { } In 2.12.4 the field is dropped entirely.

Version information 2.12.4

To Reproduce

This first test passes with Jackson 2.10.5 and fails in 2.12.4. The second test passes in both versions.

class JacksonTest {
    @Test
    fun `will serialize a data class with a property named isOpen if it is not a boolean`() {
        data class Thing(val isOpen: Map<String, Boolean>)

        val data = Thing(isOpen = mapOf(Pair("a", true)))

        ObjectMapper().writeValueAsString(data) shouldBe """{"isOpen":{"a":true}}"""
    }

    @Test
    fun `will serialize a data class with a property named isOpen if it is a boolean`() {
        data class Thing(val isOpen: Boolean)

        val data = Thing(isOpen = true)

        ObjectMapper().writeValueAsString(data) shouldBe """{"open":true}"""
    }
}

kyleboon avatar Dec 08 '21 19:12 kyleboon

@kyleboon Is this behaviour mentioned anywhere in any CHANGELOG ? I couldn't find anything but can confirm that I am also seeing the same behaviour while updating form version 2.9.8 to 2.12.4.

sudo-nan0-RaySK avatar Feb 23 '22 17:02 sudo-nan0-RaySK

Is this with Kotlin? If so, the right project is jackson-module-kotlin, not jackson-databind -- I can transfer but want to make sure first. Kotlin's logic for "is propeties" differs from Java Beans spec and there have been changes in this area.

cowtowncoder avatar Feb 24 '22 03:02 cowtowncoder

Sorry for the late response, yes this was with kotlin

kyleboon avatar Mar 07 '22 15:03 kyleboon

Same happening with Java as well.

sudo-nan0-RaySK avatar Mar 08 '22 18:03 sudo-nan0-RaySK

@sudo-nan0-RaySK I don't think this should be the case, unless Kotlin module is registered. If it does occur, this would belong here, but for now I assume it is Kotlin-MODULE specific (not necessarily language/value however; but module does not discriminate).

cowtowncoder avatar Mar 08 '22 18:03 cowtowncoder

In 2.15 or later, the following is the correct behavior when using KotlinModule.

import org.junit.Test
import kotlin.test.assertEquals

class JacksonTest {
    @Test
    fun `will serialize a data class with a property named isOpen if it is not a boolean`() {
        data class Thing(val isOpen: Map<String, Boolean>)

        val data = Thing(isOpen = mapOf(Pair("a", true)))

        assertEquals("""{"isOpen":{"a":true}}""", jacksonObjectMapper().writeValueAsString(data))
    }

    @Test
    fun `will serialize a data class with a property named isOpen if it is a boolean`() {
        data class Thing(val isOpen: Boolean)

        val data = Thing(isOpen = true)

        assertEquals("""{"isOpen":true}""", jacksonObjectMapper().writeValueAsString(data))
    }
}

This issue is closed because the default behavior has changed.

k163377 avatar Mar 18 '23 19:03 k163377

The above test still fails for me.

geardaddie avatar Jul 07 '23 20:07 geardaddie

@geardaddie Please share the test cases and the versions you used.

k163377 avatar Jul 08 '23 10:07 k163377

tl;dr - Using 2.15.1 without registering the KotlinModule, test still fails as Kyle described. Using 2.15.1 with registering the KotlinModule inverts the pass/fail results with it no longer serializing the property isOpen as "open"

Ah.. I missed that the test was changed. It now uses "isOpen" in both cases vs using "open" for boolean variables. Isn't that a breaking change?

For reference: I'm using Kotlin 1.8.20, Jackson 2.15.1

It looks like I get the proper "boolean" behavior if I don't register the Kotlin module, but the serialization of the map starting with "isOpen" doesn't work (works as it did in the original post). If I register the Kotlin module, then the "isOpen" map works correctly, but the boolean "isOpen" is written as "isOpen".

As for test case, I'm using exactly what Kyle posted originally.
Only I've tried changing is instead of using "ObjectMapper()", I tried registering the Kotlin Module "ObjectMapper() .registerKotlinModule()"

geardaddie avatar Jul 10 '23 20:07 geardaddie

@k163377 I'm actually getting the above test to fail with 2.10.5 with a parse error when using the Kotlin module. So... hold off on doing any digging.

Error on 2.10.5 with the Kotlin module registered

Signature Parse error: expected '<' or ';' but got  
	Remaining input:  - will serialize a data class with a property named isOpen if it is not a boolean$Thing;
java.lang.reflect.GenericSignatureFormatError: Signature Parse error: expected '<' or ';' but got  
	Remaining input:  - will serialize a data class with a property named isOpen if it is not a boolean$Thing;

geardaddie avatar Jul 10 '23 20:07 geardaddie

Wrt behavior differing between Kotlin module being registered and not -- Kotlin's handling of "is-getters" is fundamentally different from Java Bean naming. So difference in that sense is expected. Changes otherwise intend to get closer to Kotlin semantics so -- in general -- later versions' behavior is more correct and even if breaking are towards getting things to work better (fixing problems). That is not always what ends up happening of course, but the intent.

cowtowncoder avatar Jul 10 '23 21:07 cowtowncoder