FasterXML / jackson-module-kotlin

Module that adds support for serialization/deserialization of Kotlin (http://kotlinlang.org) classes and data classes.
Apache License 2.0
1.13k stars 175 forks source link

@JsonProperty is ignored on data class properties with names starting with "is" #237

Closed MartinHaeusler closed 1 year ago

MartinHaeusler commented 5 years ago

Hi,

I've got a data class with a boolean property called isPublic. This property needs to be called exactly like that in JSON. However, since the property name starts with is, the prefix is stripped from the name. Adding @JsonProperty("isPublic") does not work either:

class JacksonTest {

    @Test
    fun serializeTest(){
        val objectMapper = ObjectMapper()
        objectMapper.registerKotlinModule()

        val instance = MyClass(true)
        val json = objectMapper.writeValueAsString(instance)

        assertEquals("""{"isPublic":true}""", json.replace("""\s+""".toRegex(), ""))
    }

    data class MyClass(
        @JsonProperty("isPublic")
        val isPublic: Boolean
    )

}

... fails with an assertion error:

java.lang.AssertionError: Expected <{"isPublic":true}>, actual <{"public":true}>.

It doesn't matter what you pass into the @JsonProperty annotation, in the JSON output the field is always (!!) called public.

MartinHaeusler commented 5 years ago

I just figured out that this only happens if the field name starts with is. On all other field names, the @JsonProperty annotation is respected as intended.

ghost commented 5 years ago

This problem is extra extra confusing when using an event based framework like Axon with Kotlin. A boolean with is in the name will always come out false inside of the event since Axon uses Jackson internally to serialize and deserialize events.

This turned out to be a huge waste of time until one of us remembered this was a known issue. Is there any plan to fix this problem?

cowtowncoder commented 5 years ago

I am not aware of anyone working on the issue. New contributors to Kotlin module would be highly appreciated, as its usage has been increasing rapidly along with adoption of Kotlin. But I don't personally have enough foundational knowledge of Kotlin as platform to be of much help unfortunately. I could try a Bat Signal on mailing lists, but I have rarely had much success when asking for something other than very specific help (f.ex I did get contributors to help with Scala module release). Ownership is something that you can't really sell or advertise for.

rafalschmidt97 commented 5 years ago

Same! Any solution? I thought that I made mistake somewhere in the configuration.

My code:

data class PlayerResponse(
  @JsonProperty("abc")
  val accountId: Long,

  @JsonProperty("def")
  val isAdmin: Boolean,

  @JsonProperty("ghi")
  val isConnected: Boolean
)

val mapper = jacksonObjectMapper().apply {
    registerModule(ParameterNamesModule())
    registerModule(Jdk8Module())
    registerModule(JavaTimeModule())
  }

  val json = mapper.writeValueAsString(PlayerResponse(1, true, true))

Response:

{"abc":1,"admin":true,"connected":true}
MartinHaeusler commented 5 years ago

The only workaround I've found is to rename the field in your (data) class, and then expose it with @JsonProperty like so:

data class MyDTO(

    @JsonProperty("isConnected")
    val connected: Boolean   // do NOT call this "isConnected"!

)

That worked for me, YMMV.

MaksimRT commented 5 years ago

Same! Any solution? I thought that I made mistake somewhere in the configuration.

My code:

data class PlayerResponse(
  @JsonProperty("abc")
  val accountId: Long,

  @JsonProperty("def")
  val isAdmin: Boolean,

  @JsonProperty("ghi")
  val isConnected: Boolean
)

val mapper = jacksonObjectMapper().apply {
    registerModule(ParameterNamesModule())
    registerModule(Jdk8Module())
    registerModule(JavaTimeModule())
  }

  val json = mapper.writeValueAsString(PlayerResponse(1, true, true))

Response:

{"abc":1,"admin":true,"connected":true}

Use @get:JsonProperty("abc") and @param:JsonProperty("abc") annotation on Boolean field instead of @JsonProperty("abc")

rafalschmidt97 commented 5 years ago

@MaksimRT - wow it works. Thanks a lot!

apatrida commented 5 years ago

@MartinHaeusler does the workaround as mentioned by MaksimRT a few posts up solve your issue as well?

MartinHaeusler commented 5 years ago

I didn't try it, but it does seem plausible that it could fix the issue. The real question is: where does it come from, and can it be fixed rather than worked around? This is a very nasty bug after all, and extremely hard to search for on google, because likely you wouldn't even know what hit you.

MaksimRT commented 5 years ago

It will be fixed in 2.10.1 version by https://github.com/FasterXML/jackson-module-kotlin/pull/256

apatrida commented 5 years ago

@MartinHaeusler We are trying to find a way to deal with where annotations are placed and how they are interpreted, the issue is that Kotlin compiler chooses where they go, and it picks a spot that is far from good in terms of Jackson seeing them and being able to interpret them correctly.

I am looking to see if there are options to "imply" them where the are missing, or see if we can get Kotlin to consider placing them not in the "first" place it finds that matches their list, but ALL places we want them.

So it is more of incompatibility with Kotlin annotation targetting than a bug in Jackson Kotlin module. When the annotations are where we can see them safely, things work. If they end up going somewhere silly, not so much.

It is a current issue we want to solve for sure.

erika33 commented 4 years ago

@MartinHaeusler

MartinHaeusler commented 4 years ago

@erika33 yes?

ishanbakshi commented 4 years ago

Thanks @MaksimRT
Just adding @get:JsonProperty("abc") worked for me. I did not add @param:JsonProperty("abc") and it still worked. Is there any specific reason why you added @param:JsonProperty("abc") as well?

MaksimRT commented 4 years ago

Thanks @MaksimRT Just adding @get:JsonProperty("abc") worked for me. I did not add @param:JsonProperty("abc") and it still worked. Is there any specific reason why you added @param:JsonProperty("abc") as well?

Hi, you are welcome)

What version of the library do you use?)

mkheck commented 3 years ago

Changing @JsonProperty("fieldname") to @field:JsonProperty("fieldname") resolves the issue for data classes' "is" (Boolean) properties.

k163377 commented 1 year ago

I have checked the following code and it seems to have already been resolved, so this issue is closed.

import com.fasterxml.jackson.annotation.JsonProperty

data class PlayerResponse(
    @JsonProperty("abc")
    val accountId: Long,

    @JsonProperty("def")
    val isAdmin: Boolean,

    @JsonProperty("ghi")
    val isConnected: Boolean
)

fun main() {
    val mapper = jacksonObjectMapper()
    val json = mapper.writeValueAsString(PlayerResponse(1, true, true))
    println(json)
}