Jackson Top

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE

1. Overview

In this tutorial, we’ll discuss the Jackson support for Kotlin.

We’ll explore how to serialize and deserialize both Objects and Collections. We’ll also make use of @JsonProperty and @JsonInclude annotations.

2. Maven Configuration

First, we need to add the jackson-module-kotlin dependency to our pom.xml:

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

The latest version of jackson-module-kotlin can be found on Maven Central.

3. Object Serialization

Let’s start with object serialization.

Here we have a simple data Movie class that we’ll use in our examples:

data class Movie(
  var name: String, 
  var studio: String, 
  var rating: Float? = 1f)

In order to serialize and deserialize objects, we’ll need to have an instance of ObjectMapper for Kotlin.

We can create one using jacksonObjectMapper():

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

val mapper = jacksonObjectMapper()

Or we can create an ObjectMapper and then register KotlinModule:

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.KotlinModule

val mapper = ObjectMapper().registerModule(KotlinModule())

Now that we have our mapper, let’s use it to serialize a simple Movie object.

We can serialize an object to a JSON string using the method writeValueAsString():

@Test
fun whenSerializeMovie_thenSuccess() {               
    val movie = Movie("Endgame", "Marvel", 9.2f)
    val serialized = mapper.writeValueAsString(movie)
    
    val json = """
      {
        "name":"Endgame",
        "studio":"Marvel",
        "rating":9.2
      }"""
    assertEquals(serialized, json)      
}

4. Object Deserialization

Next, we’ll use our mapper to deserialize a JSON String to a Movie instance.

We’ll use the method readValue():

@Test
fun whenDeserializeMovie_thenSuccess() {               
    val json = """{"name":"Endgame","studio":"Marvel","rating":9.2}"""
    val movie: Movie = mapper.readValue(json)
    
    assertEquals(movie.name, "Endgame")   
    assertEquals(movie.studio, "Marvel")   
    assertEquals(movie.rating, 9.2f)   
}

Note that we don’t need to provide TypeReference to readValue() method; we only need to specify the variable type.

We can also specify the class type in a different way:

val movie = mapper.readValue<Movie>(json)

While deserializing, if a field is missing from JSON String, the mapper will use the default value declared in our class for that field.

Here our JSON String is missing rating field, so the default value 1 was used:

@Test
fun whenDeserializeMovieWithMissingValue_thenUseDefaultValue() {               
    val json = """{"name":"Endgame","studio":"Marvel"}"""
    val movie: Movie = mapper.readValue(json)
    
    assertEquals(movie.name, "Endgame")   
    assertEquals(movie.studio, "Marvel")   
    assertEquals(movie.rating, 1f)   
}

5. Working with Maps

Next, we’ll see how to serialize and deserialize Maps using Jackson.

Here we’ll serialize a simple Map<Int, String>:

@Test
fun whenSerializeMap_thenSuccess() {               
    val map =  mapOf(1 to "one", 2 to "two")
    val serialized = mapper.writeValueAsString(map)
    
    val json = """
      {
        "1":"one",
        "2":"two"
      }"""
    assertEquals(serialized, json)      
}

Next, when we deserialize the map, we need to make sure to specify the key and value types:

@Test
fun whenDeserializeMap_thenSuccess() {               
    val json = """{"1":"one","2":"two"}"""
    val aMap: Map<Int,String> = mapper.readValue(json)
    
    assertEquals(aMap[1], "one")   
    assertEquals(aMap[2], "two")   
}

6. Working with Collections

Now, we’ll see how to serialize collections in Kotlin.

Here we have a List of movies that we want to serialize to a JSON String:

@Test
fun whenSerializeList_thenSuccess() {
    val movie1 =  Movie("Endgame", "Marvel", 9.2f)
    val movie2 =  Movie("Shazam", "Warner Bros", 7.6f)
    val movieList = listOf(movie1, movie2)
    val serialized = mapper.writeValueAsString(movieList)
    
    val json = """
      [
        {
          "name":"Endgame",
          "studio":"Marvel",
          "rating":9.2
        },
        {
          "name":"Shazam",
          "studio":"Warner Bros",
          "rating":7.6
        }
      ]"""
    assertEquals(serialized, json)        
}

Now when we deserialize a List, we need to provide the object type Movie – just like we did with the Map:

@Test
fun whenDeserializeList_thenSuccess() {
    val json = """[{"name":"Endgame","studio":"Marvel","rating":9.2}, 
      {"name":"Shazam","studio":"Warner Bros","rating":7.6}]"""
    val movieList: List<Movie> = mapper.readValue(json)
        
    val movie1 =  Movie("Endgame", "Marvel", 9.2f)
    val movie2 =  Movie("Shazam", "Warner Bros", 7.6f)
    assertTrue(movieList.contains(movie1))                    
    assertTrue(movieList.contains(movie2))                    
}

7. Changing a Field Name

Next, we can change a field name during serialization and deserialization using @JsonProperty annotation.

In this example, we’ll rename the authorName field to author for a Book data class:

data class Book(
  var title: String, 
  @JsonProperty("author") var authorName: String)

Now when we serialize a Book object, author is used instead of authorName:

@Test
fun whenSerializeBook_thenSuccess() {               
    val book =  Book("Oliver Twist", "Charles Dickens")
    val serialized = mapper.writeValueAsString(book)
    
    val json = """
      {
        "title":"Oliver Twist",
        "author":"Charles Dickens"
      }"""
    assertEquals(serialized, json)      
}

The same goes for deserialization as well:

@Test
fun whenDeserializeBook_thenSuccess() {               
    val json = """{"title":"Oliver Twist", "author":"Charles Dickens"}"""
    val book: Book = mapper.readValue(json)
    
    assertEquals(book.title, "Oliver Twist")   
    assertEquals(book.authorName, "Charles Dickens")   
}

8. Excluding Empty Fields

Finally, we’ll discuss how to exclude empty fields from serialization.

Let’s add a new field called genres to the Book class. This field is initialized as emptyList() by default:

data class Book(
  var title: String, 
  @JsonProperty("author") var authorName: String) {
    var genres: List<String>? = emptyList()
}

All fields are included by default in serialization – even if they are null or empty:

@Test
fun whenSerializeBook_thenSuccess() {               
    val book =  Book("Oliver Twist", "Charles Dickens")
    val serialized = mapper.writeValueAsString(book)
    
    val json = """
      {
        "title":"Oliver Twist",
        "author":"Charles Dickens",
        "genres":[]
      }"""
    assertEquals(serialized, json)      
}

We can exclude empty fields from JSON using @JsonInclude annotation:

@JsonInclude(JsonInclude.Include.NON_EMPTY)
data class Book(
  var title: String, 
  @JsonProperty("author") var authorName: String) {
    var genres: List<String>? = emptyList()
}

That will exclude fields that are null, an empty Collection, an empty Maps, an array with zero length, and so on:

@Test
fun givenJsonInclude_whenSerializeBook_thenEmptyFieldExcluded() {               
    val book =  Book("Oliver Twist", "Charles Dickens")
    val serialized = mapper.writeValueAsString(book)
    
    val json = """
      {
        "title":"Oliver Twist",
        "author":"Charles Dickens"
      }"""
    assertEquals(serialized, json)      
}

9. Conclusion

In this article, we learned how to use Jackson to serialize and deserialize objects in Kotlin.

We also learned how to use @JsonProperty and @JsonInclude annotations.

And, as always, the full source code can be found over on GitHub.

Jackson bottom

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE