Trusted answers to developer questions
Trusted Answers to Developer Questions

Related Tags

kotlin
api
parse
jackson
communitycreator

Parsing API data in Kotlin using Jackson

Stephen Roberts

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.

What is Jackson?

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.

Parsing API data

A sample of the demo response that we’ll be working with is detailed below:

Sample response data from API
Sample response data from API

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.

Basic parse - JsonNode

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")
Importing Jackson module dependency in build.gradle.kts file

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()
Instantiation of 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
    }
}
Parsing a response body into JSON

…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"])
}
Accessing nested elements within a JSON object

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:

Output from makeRequest function
Output from makeRequest function

Our code looks as follows:

package com.example.httprequest

import ...

val objectMapper = jacksonObjectMapper()

@EnableScheduling
@Repository
class 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
    }
}
Repository code for simple JSON format

That’s great to start with, but it has some drawbacks:

  • We have no control over the data that we’re provided.
  • What if we’re missing some data; for example, if the "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.

DTO parse

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.httprequest

import com.fasterxml.jackson.annotation.JsonIgnoreProperties

data 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
)
Data Transfer Object

There are a few things in the above snippet that I’d like to point out:

  • At each level, we’re defining a new data class to use, giving us control over our data set
  • Using our own data classes gives us the ability to set nullable values
  • We’re using the @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
    }
Parsing a response body into DTO
@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)
}
Accessing nested elements within a DTO

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.httprequest

import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.springframework.scheduling.annotation.EnableScheduling
import org.springframework.scheduling.annotation.Scheduled
import org.springframework.stereotype.Repository

val objectMapper = jacksonObjectMapper()

@EnableScheduling
@Repository
class 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.

Further reading

Jackson for Kotlin


If you enjoyed this type of content and would like to see more, feel free to check out my social media accounts:

Twitter: @sdrobertsCode
LinkedIn
GitHub

RELATED TAGS

kotlin
api
parse
jackson
communitycreator
RELATED COURSES

View all Courses

Keep Exploring