Dealing with two different names for same field in Api response with spring boot Mapper
We often make changes in api, and changing names of same field in different api version leads us to make change in the way we are handling response in backend.
Suppose you have a api, when you use to call it from backend, it gives following response:
{
"id": 1,
"name": "Flowers",
"producedBy": "Kid Harpoon",
"singer": "Miley Cyrus"
}
In next version of api, you choose to go for snake case, so you changed api structure to :
{
"id": 1,
"name": "Flowers",
"produced_by": "Kid Harpoon",
"singer": "Miley Cyrus"
}
After receiving this response, we deserialise the response body to a single object or a stream of objects, respectively.
Initially you were handling this way when producedBy field was there:
import kotlinx.coroutines.*
import org.springframework.web.reactive.function.client.WebClient
data class Song(
val id: Int,
val name: String,
val producedBy: String,
val singer:String
)
suspend fun main() {
val client = WebClient.create("https://api-url.com")
val user = withContext(Dispatchers.IO) {
client.get()
.uri("/songs/1")
.retrieve()
.awaitBody<Song>()
}
println(user)
}
Now for produced_by you will think, lets add a JSON Property annotation to handle produced_by field from response like this:
import kotlinx.coroutines.*
import org.springframework.web.reactive.function.client.WebClient
data class Song(
val id: Int,
val name: String,
@JsonProperty("produced_by")
val producedBy: String,
val singer:String
)
suspend fun main() {
val client = WebClient.create("https://api-url.com")
val user = withContext(Dispatchers.IO) {
client.get()
.uri("/songs/1")
.retrieve()
.awaitBody<Song>()
}
println(user)
}
@JsonProperty
is used to map a Java object property to a JSON property with a different name, or to override the default mapping.
But imagine if you push this code to production and your api is not updated to have produced_by field, or maybe your api is updated to have produced_by field but because of some reason they revert back the api to previous version. In this scenario, you code will break in production. It will not store any value in producedBy field of Song object, and man you lost data in production, wait WHAT?
Yeah, your code is not backward compatible. But Jackson, our Java library for working with JSON data, helps us here. There are two annotation : JsonProperty
and JsonAlias
that can be used to control the serialization and deserialization of JSON objects. Both @JsonProperty
and @JsonAlias
annotations can be used together to provide more control over the serialization and deserialization of JSON data in Java objects.
@JsonAlias
is used to specify alternative names for a JSON property that can be used during deserialization. @JsonAlias
annotation can be used to specify that the producedBy
field can be deserialized from either the "producedBy", "produced_by" and “producer” in the incoming JSON. This allows you to handle JSON data from different sources that use different property names for the same data.
This is how your updated code will look like:
import kotlinx.coroutines.*
import org.springframework.web.reactive.function.client.WebClient
data class Song(
val id: Int,
val name: String,
@JsonAlias({"producer", "produced_by"})
val producedBy: String,
val singer:String
)
suspend fun main() {
val client = WebClient.create("https://api-url.com")
val user = withContext(Dispatchers.IO) {
client.get()
.uri("/songs/1")
.retrieve()
.awaitBody<Song>()
}
println(user)
}
Thats it. Now you won’t lose the data in production and your system is backward compatible. Hurrayyyyy! now you are awesome developer because you think of failing scenarios and made your system backward compatible.