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 User
s and serialize them into a JSON array:
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.