VSMent
VSMent

Reputation: 426

Jackson JsonIgnore does not work in kotlin

I am rewriting Java Spring application to Kotlin Spring application.

All works fine except API request to openweather.

To store DTO in database there is id field alongside with cityId retrieved from API (it is called id there).

For some reason @JsonIgnore does not work for DTO id field.

build.gradle

// plugins

    id 'org.springframework.boot' version '2.2.4.RELEASE'
    id 'io.spring.dependency-management' version '1.0.9.RELEASE'
    id 'java'
    id 'war'
    id 'maven'
    id 'org.jetbrains.kotlin.jvm' version '1.3.70'
    id "org.jetbrains.kotlin.plugin.jpa" version "1.3.70"
    id "org.jetbrains.kotlin.plugin.noarg" version "1.3.70"
    id "org.jetbrains.kotlin.plugin.spring" version "1.3.70"
    id "org.jetbrains.kotlin.plugin.allopen" version "1.3.70"


// dependencies

    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    implementation 'org.springframework.boot:spring-boot-starter-security'
    implementation 'org.springframework.boot:spring-boot-starter-mail:2.2.4.RELEASE'
    implementation 'org.springframework.security:spring-security-test'
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
    implementation "org.jetbrains.kotlin:kotlin-reflect"
    implementation "com.fasterxml.jackson.module:jackson-module-kotlin:2.10.2"

OpenWeather responds with next JSON (some fields omitted):

{
    "coord":{
        "lon":-0.13,
        "lat":51.51
    },
    "main":{
        "temp":14.04,
        "feels_like":7.05,
        "pressure":1011,
        "humidity":61
    },
    "dt":1584018901,
    "id":2643743,              <- cityId in DTO class
    "name":"London",
 ...
}

DTO class:


import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import com.fasterxml.jackson.annotation.JsonProperty

@JsonIgnoreProperties(ignoreUnknown = true)
data class OpenWeatherApiDto(
        @JsonIgnore
        override var id: Long? = null,
        ...
        override var cityId: Int? = null,
        ...
) : AbstractWeatherSampleDto() {
    ...
    @JsonProperty("id")
    private fun unpackId(idObj: Int?) {
        cityId = idObj
                ?: throw ApiFieldNotFoundException("id")
    }
    ...
}

Test that fails

@Test
    fun createFromFile() {
        val mapper = jacksonObjectMapper()
        Files.lines(Paths.get("src/test/resources/data/OWApi.json")).use { s ->
            val json: String = s.collect(Collectors.joining())
            val ws: OpenWeatherApiDto = mapper.readValue(json)
            println(ws)
            assertThat(ws)
                    .isNotNull
                    .extracting("cityId").isEqualTo(2643743)
        }
    }

Fail message is:

[Extracted: cityId] 
Expecting:
 <null>
to be equal to:
 <2643743>
but was not.

And actual object is:

OpenWeatherApiDto(id=2643743, cityName=London, temperature=14.04, feelsLike=7.05, pressure=1011.0, humidity=61, clouds=null, cityId=null, time=1584018901, latitude=51.51, longitude=-0.13)

I've found similar issue on jackson-module-kotlin GitHub page which happened to be jackson-databind related and resolved in 2.9.6 while I use 2.10.2

Upvotes: 6

Views: 10619

Answers (3)

Bobin
Bobin

Reputation: 425

I had to remove @Serializable from my class in order for @JsonIgnore to work on the field. Very strange.

Upvotes: 0

VSMent
VSMent

Reputation: 426

I did not found the solution, but with

    @JsonProperty("_id")
    override var id: Long? = null,

it works as expected.

Upvotes: 1

gidds
gidds

Reputation: 18537

I haven't tried it with the exact versions you're using, but I suspect it'll work if you replace @JsonIgnore with @get:JsonIgnore.

This is one of those common gotchas when converting Java to Kotlin.  In Java, you usually implement a property with a private field and a getter method (and a setter if it's mutable).  You might also include a constructor parameter to initialise it from.  That's up to four separate bits of code, all related to the same property — and each can have their own annotations.

However, in Kotlin, you can get all of those from a single bit of code.  (Any non-private property gives you a private field and a getter method; if it's var you also get a setter; and if it's in the primary constructor then you also get a constructor parameter.)  Which is far more concise!

But what do any annotations apply to?

By default, they apply to the constructor param — or, if the property is defined in the class body, the property itself (which is visible only to Kotlin).  So if you want them to apply to the field, or to the getter or setter, you need to specify that explicitly, with e.g. @field:JsonIgnore or @get:JsonIgnore.

Full details are in the Kotlin docs.

Upvotes: 26

Related Questions