Reputation: 426
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
Reputation: 425
I had to remove @Serializable from my class in order for @JsonIgnore to work on the field. Very strange.
Upvotes: 0
Reputation: 426
I did not found the solution, but with
@JsonProperty("_id")
override var id: Long? = null,
it works as expected.
Upvotes: 1
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