Following my previous article, this post will discuss how to parse API data in Kotlin using the Jackson serialization/deserialization module.
You can find the code for this post under the following repo.
Jackson is a library that helps us serialize and deserialize data into a format that best suits our needs. Jackson is used extensively in Java applications and now has a fantastic Kotlin-compatible module.
A sample of the demo response that we’ll be working with is detailed below:
As shown, the results are passed back to us in a JSON format. Let’s get Jackson to deserialize our response body into a malleable JSON blob.
Firstly, we need to import the Jackson module for Kotlin dependency into our build.gradle.kts file:
implementation(dependencyNotation: "com.fasterxml.jackson.module:jackson-module-kotlin:2.9.7")
Next, we need an instance of the JacksonObjectMapper
in our Repository layer. This enables us to use the functions Jackson provides for parsing data:
val objectMapper = jacksonObjectMapper()
Now, we can use the objectMapper
to read the value of our response body and parse it into a JsonNode. The JsonNode is then passed back to the function that invokes parseResponse()
:
fun parseResponse(response : Response): JsonNode {val body = response.body?.string() ?: ""val jsonBody = objectMapper.readValue<JsonNode>(body)return jsonBody}}
…which is the makeRequest()
function:
@Scheduled(initialDelay = 0, fixedDelay = 24*60*60*1000)fun makeRequest() {val okHttpClient = OkHttpClient()val parsedResponse = parseResponse(okHttpClient.newCall(createRequest()).execute())println(parsedResponse["api"]["teams"][0]["name"])}
The above snippet prints an extract from the JSON response. Here, we’re accessing the first team’s name from within the "api"
response.
And that’s it! We now have a response that is parsed into a workable JSON object:
Our code looks as follows:
package com.example.httprequestimport ...val objectMapper = jacksonObjectMapper()@EnableScheduling@Repositoryclass Repository(){@Scheduled(initialDelay = 0, fixedDelay = 24*60*60*1000)fun makeRequest() {val okHttpClient = OkHttpClient()val parsedResponse = parseResponse(okHttpClient.newCall(createRequest()).execute())println(parsedResponse["api"]["teams"][0]["name"])}fun createRequest(): Request {return Request.Builder().url("https://www.api-football.com/demo/api/v2/teams/league/524").build()}fun parseResponse(response: Response): JsonNode {val body = response.body?.string() ?: ""val jsonBody = objectMapper.readValue<JsonNode>(body)return jsonBody}}
That’s great to start with, but it has some drawbacks:
"teams"
node doesn’t return anything? Here, the application would throw a null pointer exception when it tries to pick up the 0th element of the array.To combat these issues, we can convert the JSON object into a Data Transfer Object that we define.
As above, we’ll import the dependency and instantiate our objectMapper
.
Next, we need to define our Data Transfer Object. This will try to map the data from the response that we actually care about. We will do this in its own file, DTO.kt
:
package com.example.httprequestimport com.fasterxml.jackson.annotation.JsonIgnorePropertiesdata class DTO(val api: ApiResponse)@JsonIgnoreProperties(ignoreUnknown = true)data class ApiResponse(val results: Int,val teams: List<Team>)@JsonIgnoreProperties(ignoreUnknown = true)data class Team(val team_id: Int,val name: String,val country: String,val venue_name: String)
There are a few things in the above snippet that I’d like to point out:
@JsonIgnoreProperties
annotation and set ignoreUnknown = true
. This tells the objectMapper
not to worry if it reads a value that isn’t in the data class - it will just ignore the value and move onto the next.So, we have our data classes set up. Now we just need to adjust our parsing code to use that type rather than a JsonNode
:
fun parseResponse(response: Response): DTO {val body = response.body?.string() ?: ""val jsonBody = objectMapper.readValue<DTO>(body)return jsonBody}
@Scheduled(initialDelay = 0, fixedDelay = 24*60*60*1000)fun makeRequest() {val okHttpClient = OkHttpClient()val parsedResponse = parseResponse(okHttpClient.newCall(createRequest()).execute())println(parsedResponse.api.teams.first().name)}
Here, we’ve moved away from the array operator (square brackets) to access various elements. Instead, we’re using the attributes within our data classes. A useful side effect of this is the IDE’s code-completion that assists by showing what elements you can have access to when writing your code.
With the DTO classes in place, here is our final code:
package com.example.httprequestimport com.fasterxml.jackson.module.kotlin.jacksonObjectMapperimport com.fasterxml.jackson.module.kotlin.readValueimport okhttp3.OkHttpClientimport okhttp3.Requestimport okhttp3.Responseimport org.springframework.scheduling.annotation.EnableSchedulingimport org.springframework.scheduling.annotation.Scheduledimport org.springframework.stereotype.Repositoryval objectMapper = jacksonObjectMapper()@EnableScheduling@Repositoryclass Repository(){@Scheduled(initialDelay = 0, fixedDelay = 24*60*60*1000)fun makeRequest() {val okHttpClient = OkHttpClient()val parsedResponse = parseResponse(okHttpClient.newCall(createRequest()).execute())println(parsedResponse.api.teams.first().name)}fun createRequest(): Request {return Request.Builder().url("https://www.api-football.com/demo/api/v2/teams/league/524").build()}fun parseResponse(response: Response): DTO {val body = response.body?.string() ?: ""val jsonBody = objectMapper.readValue<DTO>(body)return jsonBody}}
That’s it - we’re now able to call an external API and deserialize the response into a workable format. Next, we’ll learn how to save our results in a database.
If you enjoyed this type of content and would like to see more, feel free to check out my social media accounts:
Free Resources