Reading and Writing JSON in Kotlin with Jackson

In this article we'll be taking a look at how to read and write JSON files in Kotlin, specifically, using the Jackson library.

Jackson Dependency

To utilize Jackson, we'll want to add its jackson-module-kotlin dependency to our project. If you're using Maven, you can simply add:

<dependency>
    <groupId>com.fasterxml.jackson.module</groupId>
    <artifactId>jackson-module-kotlin</artifactId>
    <version>2.12.1</version>
</dependency>

Or, if you're using Gradle, you can add:

implementation 'com.fasterxml.jackson.module:jackson-module-kotlin:2.12.1'

With the dependency in place, let's define a JSON object we'll want to read:

{
   "id":101,
   "username":"admin",
   "password":"Admin123",
   "fullName":"Best Admin"
}

Reading a JSON Object to Kotlin Object

Let's take a look at how we can deserialize a JSON object into a Kotlin object. Since we'll want to convert the JSON contents into a Kotlin object, let's define a User data class:

data class User (
    val id: Int,
    val username: String,
    val password: String,
    val fullName: String
)

Then, we'll want to instantiate the object mapper, which can easily be done with:

import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper

// Registering the Kotlin module with the ObjectMapper instance
val mapper = jacksonObjectMapper()

// JSON String
val jsonString = """{
    "id":101,
    "username":"admin",
    "password":"Admin123",
    "fullName":"Best Admin"
    }"""

With that done, we can pass JSON contents into our ObjectMapper methods, such as readValue(), as usual for Jackson:

// Read data from a JSON string
val userFromJson = mapper.readValue<User>(jsonString)
// Or
val userFromJsonWithType: User = mapper.readValue(jsonString)

If we print the value of userFromJson we'll see something like this:

User(id=101, username=admin, password=Admin123, fullName=Best Admin)

The readValue() function can be used with or without the Class parameter, as you saw a little earlier with the two variables (userFromJson and userFromJsonWithType).

Note: Using the function without the Class parameter will materialize the type and automatically create a TypeReference for Jackson.

Writing a JSON Object From Kotlin Object

Now, let's take a look at how we can serialize a Kotlin object into a JSON object.

We'll use the same data class (User) and play a little with the features of Jackson:

val user = User(102, "test", "pass12", "Test User")
val userJson = mapper.writeValueAsString(user)
println(userJson)

Here, we've instantiated a new instance of the User class, with a few values.

The writeValueAsString() function is our key player here and serializes any object and its fields as a String.

Let's print the userFromJson String variable and see how it looks like:

{
   "id":102,
   "username":"test",
   "password":"pass12",
   "fullName":"Test User"
}

Writing Kotlin List to JSON Array

Oftentimes, we're dealing with lists and arrays, rather than singular objects. Let's take a look at how we can serialize a Kotlin List into a JSON array.

Using the same data class, we'll create a list of Users and serialize them into a JSON array:

Free eBook: Git Essentials

Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!

val userList = mutableListOf<User>()
userList.add(User(102, "jsmith", "P@ss", "John Smith"))
userList.add(User(103, "janed", "Pass1", "Jane Doe"))

val jsonArray = mapper.writeValueAsString(userList)
println(jsonArray)

This results in:

[
   {
      "id":102,
      "username":"jsmith",
      "password":"P@ss",
      "fullName":"John Smith"
   },
   {
      "id":103,
      "username":"janed",
      "password":"Pass1",
      "fullName":"Jane Doe"
   }
]

Reading JSON Array to Kotlin List

Now, let's go the other way around, and deserialize a JSON array into a Kotlin List:

val userListFromJson: List<User> = mapper.readValue(jsonArray)
println(userListFromJson)

And this results in:

[User(id=102, username=test, password=pass12, fullName=Test User), User(id=103, username=user, password=Pass1, fullName=Demo User)]

Handling JSON Bidirectional Relationships

Another common thing to encounter is bidirectional relationships. In a lot of cases, you might want to have a one-to-many or many-to-one relationship between some objects.

For this, let's create a data class Author which can contain one or more objects of type Book and the Book can have one Author:

data class Author (
    val name: String,
    val books: MutableList<Book>
)

data class Book (
    val title: String,
    val author: Author
)

The Author contains a list, called books, of type Book. At the same time, the Book contains an Author instance.

Let's make a few instances and try to serialize them:

val author = Author("JK Rowling", mutableListOf())
val bookOne = Book("Harry Potter 1", author)
val bookTwo = Book("Harry Potter 2", author)
author.books.add(bookOne)
author.books.add(bookTwo)

If we were to do the same thing as before:

val authors = mapper.writeValueAsString(author)

We'd quickly run into an issue:

com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError) (through reference chain: Book["author"]->Author["books"]->...

The Author contains the Book, which contains the Author, which contains the Book, which contains the Author, and so on, until a StackOverflowError is thrown.

This is thankfully, easily fixable, using the @JsonManagedReference and @JsonBackReference annotations. We let Jackson know about this bidirectional relationship, that could go on forever:

data class Author (
    val name: String,
    @JsonManagedReference
    val books: MutableList<Book>
);

data class Book (
    val title: String,
    @JsonBackReference
    val author: Author
);

@JsonManagedReference specifies the attribute that will be serialized normally and the @JsonBackReference will specify the attribute omitted from serialization. We'll effectively take out the author reference from the Book when each is serialized.

Now, we can serialize the authors:

val authors = mapper.writeValueAsString(author)
println(authors)

If we run this code, it'll produce:

{
   "name":"JK Rowling",
   "books":[
      {
         "title":"Harry Potter 1"
      },
      {
         "title":"Harry Potter 2"
      }
   ]
}

Conclusion

In this tutorial, we've gone over how to read and write JSON Files in Kotlin with Jackson.

We've gone over the required dependencies, how to serialize and deserialize JSON and Kotlin objects, as well as arrays and lists.

Finally, we've covered how to handle bidirectional relationships when serializing and deserializing.

Last Updated: February 27th, 2023
Was this article helpful?

© 2013-2024 Stack Abuse. All rights reserved.

AboutDisclosurePrivacyTerms