1. Overview

While developing software, we frequently need to parse JSON strings to Java and Kotlin objects. Sometimes we need to map this JSON to generic classes, such as when parsing a collection of objects.

In this article, we’ll explore the use of Gson and TypeToken to deserialize JSON to generic Kotlin objects. TypeToken captures the generic definition to tell Gson the exact object type we want to convert our JSON to.

2. Dependencies

First, we need to make sure that our Kotlin project includes the Gson dependency. Let’s look at how we can configure this library for projects that use Maven and Gradle.

2.1. Gradle

For use with Gradle, we apply the Gson dependency in the build.gradle file:

dependencies {
  implementation 'com.google.code.gson:gson:2.10.1'
}

2.2. Maven

With Maven, add the following to the pom.xml file of the project:

<dependency>
  <groupId>com.google.code.gson</groupId>
  <artifactId>gson</artifactId>
  <version>2.10.1</version>
</dependency>

3. Different Ways to Deserialize JSON to Kotlin Objects

As mentioned earlier, we seek to perform the deserialization of generic Kotlin objects with the use of Gson and TypeToken. By default, Gson can handle deserialization of statically typed Kotlin classes.

When we need to deserialize a generic class, we must create a TypeToken to help Gson understand the correct type to be deserialized. TypeToken has no public constructor, but it does have a protected constructor. This means to create an instance, we must extend it. We can do this inline with an anonymous subclass.

Throughout this section, we’re going to consider a custom Kotlin class called Book:

data class Book(
    val id: Int,
    val title: String,
    val author: String
)

Let’s consider this JSON list containing several Books as well:

[
  {
    "id": 1,
    "title": "First title",
    "author": "Micheal L"
  },
  {
    "id": 2,
    "title": "Second title",
    "author": "Enombe N"
  },
  {
    "id": 1,
    "title": "Third title",
    "author": "Steve P"
  }
]

Each of the various ways we’re about to discuss makes use of one simple method: fromJson(). Basically, the fromJson() method permits Gson to convert a JSON string to an equivalent Java or Kotlin object.

3.1. Basic Usage of Gson Without Generics

Now, considering the Book class and JSON string above, we can deserialize the JSON string explicitly to an array of Book objects:

@Test
fun `explicit deserialization of JSON to Kotlin object`() {
    val books = Gson().fromJson(jsonStr, Array<Book>::class.java)
    assertEquals(3, books.size)
    assertFalse(books.isEmpty())
    assertEquals("First title", books[0].title)
}

This is a simplistic approach to deserializing JSON to custom objects as we’ve already defined the type of object we’re deserializing to.

It’s important to note that Kotlin arrays aren’t actually generic. Although they look the same, they’re special. It is not possible to generically define arrays.

Let’s now look into various ways we can obtain Kotlin objects from JSON using Gson and TypeToken generically.

3.2. Using TypeToken from an Object Expression

In this method, we first define an object expression that extends from TypeToken, and we then parameterize it with the Java type. We use the Gson.fromJson() method with the type specified for the intended result, and this type should match the TypeToken object created.

Lastly, let’s use this object with Gson to get a list of custom objects:

@Test
fun `get list of Books with explicit TypeToken object`() {
    val bookType = object : TypeToken<List<Book>>() {}.type
    val books = Gson().fromJson<List<Book>>(jsonStr, bookType)

     assertEquals(3, books.size)
     assertFalse(books.isEmpty())
     assertEquals("Second title", books[1].title)
}

3.3. Using Gson and an Extension Function

Kotlin allows for reified type parameters to know the exact type that a generic type parameter T represents at runtime. In order to use the reified type, we need to use an inline function.

Therefore, we can wrap the creation of an anonymous object of the TypeToken class in an extension function that is inline and makes use of a reified parameter. This allows for a more fluid deserialization syntax that just needs to make use of a type parameter.

First, we create an extension function on Gson to obtain our custom Kotlin object list from a JSON string:

inline fun <reified T> Gson.fromJsonList(json: String) 
        = fromJson<List<T>>(json, object : TypeToken<List<T>>() {}.type)

Second, we use an inline function with the reified keyword to mitigate type erasure at runtime.

T is a generic type that will be inferred from the call to the inline function. Therefore, we don’t need to specify the type parameter explicitly. Lastly, object is an anonymous instance class that extends from TypeToken.

This inline function accepts a JSON string as a parameter and returns a list of custom objects specified by T.

Let’s see how to use this inline function in a simple test:

@Test
fun `get list of Books with Gson extension function`() {
    val books = Gson().fromJsonList<Book>(jsonStr)

    assertEquals(3, books.size)
    assertFalse(books.isEmpty())
    assertEquals("Second title", books[1].title)
}

3.4. Using a Generic Helper Function

Furthermore, we can deserialize a JSON string to a list of custom objects with a generic type by creating a helper function that facilitates the creation of a TypeToken class:

inline fun <reified T> genericType(): Type = object: TypeToken<T>() {}.type

Finally, let’s test our helper function:

@Test
fun `get list of Books with generic helper function`() {
    val bookType = genericType<List<Book>>()
    val gson = Gson()
    val books = gson.fromJson<List<Book>>(userJson, bookType)

    assertEquals(3, books.size)
    assertFalse(books.isEmpty())
    assertEquals("Third title", books[2].title)
}

4. Conclusion

In this article, we’ve explored how we can use Gson to deserialize JSON to custom Kotlin objects using Generics. We discussed four ways to do that, complete with concrete examples.

As always, the complete code is available over on GitHub.