Question

Use Gson TypeToken with Generic Lists in Kotlin

kotlingenericsgsontypetoken

Question

How can I deserialize JSON stored in pref.turns into a List<Turns> using Gson in Kotlin?

I tried the following code:

val turnsType = TypeToken<List<Turns>>() {}.type
val turns = Gson().fromJson(pref.turns, turnsType)

However, Kotlin reports this error:

Cannot access '<init>': it is public /*package*/ in TypeToken

What is the correct Kotlin syntax for using Gson TypeToken with a generic list?

Short Answer

You will learn why Gson needs a TypeToken for generic types such as List<Turns>, why Java-style anonymous-class syntax does not work directly in Kotlin, and how to deserialize generic JSON safely.

Concept

Gson can deserialize a simple, concrete type directly:

val turn = Gson().fromJson(json, Turns::class.java)

A generic type such as List<Turns> is different. At runtime, the JVM often knows only that the value is a List; the Turns part may no longer be available. This is called type erasure.

If Gson receives only List::class.java, it can create a list but does not reliably know which object type belongs inside it. Gson's TypeToken captures the complete type information, including the element type:

List<Turns>

In Java, developers usually create an anonymous subclass with new TypeToken<List<Turns>>() {}. Kotlin uses a different syntax for anonymous objects: object : SomeType() {}. The error occurs because TypeToken<List<Turns>>() {} tries to call the constructor directly instead of creating the required subclass.

Mental Model

Think of List as a box with a label that says only list. Gson can see that it should make a box, but it needs another label telling it what belongs inside: Turns.

TypeToken<List<Turns>> is that detailed shipping label. It tells Gson:

  • create a list;
  • read every JSON object in the list;
  • turn each object into a Turns instance.

In Kotlin, object : TypeToken<...>() {} means “create a small, one-time subclass that carries this label.”

Take Quiz

Syntax and Examples

Use an anonymous object that extends TypeToken:

import com.google.gson.Gson
import com.google.gson.reflect.TypeToken

val turnsType = object : TypeToken<List<Turns>>() {}.type
val turns: List<Turns> = Gson().fromJson(pref.turns, turnsType)

The important part is object ::

object : TypeToken<List<Turns>>() {}

It is Kotlin's equivalent of Java's anonymous subclass syntax.

For example, given this model and JSON:

data class Turns(
    val id: Int,
    val text: String
)

val json = """
    [
      {"id": 1, "text": "Hello"},
      {"id": 2, "text": "Goodbye"}
    ]
""".trimIndent()

val type = object : TypeToken<List<Turns>>() {}.type
val parsedTurns: List<Turns> = Gson().fromJson(json, type)

println(parsedTurns[0].text) // Hello

Make sure the import is Gson's public TypeToken class:

import com.google.gson.reflect.TypeToken

Do not import a similarly named class from another package.

Step by Step Execution

Consider this code:

data class Turns(val id: Int, val text: String)

val json = """[{"id": 1, "text": "Move left"}]"""
val type = object : TypeToken<List<Turns>>() {}.type
val turns: List<Turns> = Gson().fromJson(json, type)

Execution happens as follows:

  1. Turns defines the shape of one turn: an id and text.
  2. json contains a JSON array, indicated by [ and ].
  3. object : TypeToken<List<Turns>>() {} creates an anonymous TypeToken subclass.
  4. .type retrieves the runtime description of List<Turns>.
  5. fromJson sees that the top-level JSON value is a list.
  6. Gson reads each array item and creates a Turns object from it.
  7. The result is assigned to turns, which is a Kotlin List<Turns>.

Real World Use Cases

TypeToken is useful whenever your JSON target includes generic type arguments.

  • Preferences or local storage: Restore a saved list of Turns, bookmarks, or recently viewed items.
  • API responses: Parse a JSON array into List<Product>, List<Comment>, or List<User>.
  • Cached data: Read cached search results after an app restart.
  • Nested collections: Parse values such as Map<String, List<Turns>>.
  • Generic wrappers: Parse API envelopes such as ApiResponse<List<Turns>> when the response model itself is generic.
Take Quiz

Real Codebase Usage

In production code, avoid repeating object : TypeToken<...>() {}.type throughout the project. A common Kotlin pattern is an inline function with a reified type parameter:

import com.google.gson.Gson
import com.google.gson.reflect.TypeToken

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

Call it with the target type at the call site:

val turns: List<Turns> = Gson().fromJsonTyped(pref.turns)

Real applications should also handle missing or invalid persisted JSON. For example, return an empty list when the stored value is absent:

fun loadTurns(json: String?): List<Turns> {
    if (json.isNullOrBlank()) return emptyList()

    val type = object : TypeToken<List<Turns>>() {}.type
    return Gson().fromJson(json, type)
}

For untrusted input, wrap parsing in error handling because malformed JSON can throw a parsing exception.

Common Mistakes

Using Java syntax directly in Kotlin

This does not create an anonymous subclass in Kotlin:

// Incorrect
val type = TypeToken<List<Turns>>() {}.type

Use Kotlin's object expression:

// Correct
val type = object : TypeToken<List<Turns>>() {}.type

Passing only List::class.java

// Avoid for a list of custom objects
val turns = Gson().fromJson(json, List::class.java)

The generic element type is missing. Gson may produce values that behave like generic maps instead of Turns objects. Use a TypeToken<List<Turns>>.

Using the wrong import

Verify that the file imports:

import com.google.gson.reflect.TypeToken

An incorrect TypeToken import can cause confusing constructor or type errors.

Assuming stored JSON is always valid

A preference value can be missing, blank, or corrupted. Check for blank values and decide on a fallback, often emptyList().

Forgetting that Kotlin non-null properties need JSON values

If a JSON field is absent while the Kotlin model expects a non-null value, the resulting object may contain unexpected defaults or later fail when used. Ensure your model and JSON format agree, or provide sensible defaults in the data class.

Comparisons

ApproachBest forKeeps generic type information?
Turns::class.javaOne non-generic objectNot needed
List::class.javaRare cases where item type does not matterNo
object : TypeToken<List<Turns>>() {}.typeLists, maps, and nested generic typesYes
Reified Gson helperRepeated parsing in Kotlin codebasesYes

TypeToken is not required for every Gson call. It is mainly required when the target type contains generics, such as List<Turns>, Map<String, Turns>, or ApiResponse<Turns>.

Take Quiz

Cheat Sheet

import com.google.gson.Gson
import com.google.gson.reflect.TypeToken

Parse one object

val turn: Turns = Gson().fromJson(json, Turns::class.java)

Parse a generic list

val type = object : TypeToken<List<Turns>>() {}.type
val turns: List<Turns> = Gson().fromJson(json, type)

Parse a generic map

val type = object : TypeToken<Map<String, Turns>>() {}.type
val turnsById: Map<String, Turns> = Gson().fromJson(json, type)

Reusable Kotlin helper

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

Key rule

Use object : TypeToken<T>() {} in Kotlin—not TypeToken<T>() {}.

FAQ

Why does Gson need TypeToken for List<Turns>?

The runtime type List does not include its item type reliably because of JVM type erasure. TypeToken preserves the Turns information for Gson.

What is the Kotlin equivalent of new TypeToken<List<Turns>>() {}?

Use:

object : TypeToken<List<Turns>>() {}

Can I use List::class.java instead of TypeToken?

Not when you need Gson to create Turns objects inside the list. List::class.java does not describe the element type.

Which TypeToken should I import?

Use:

import com.google.gson.reflect.TypeToken

Does TypeToken work for nested generics?

Yes. For example:

val type = object : TypeToken<Map<String, List<Turns>>>() {}.type

Can I avoid writing TypeToken repeatedly?

Yes. Create an inline reified helper around Gson's fromJson method and call it with an explicit target type.

What should happen when the saved JSON is blank?

For a stored list, returning emptyList() is often appropriate. Check json.isNullOrBlank() before parsing.

Take Quiz

Mini Project

Description

Build a small preference-style loader that converts saved JSON into a list of turn records. This demonstrates how an app can restore structured local data while handling an empty stored value.

Goal

Create a function that returns a List<Turns> from JSON and returns an empty list when no JSON has been saved.

Requirements

Define a Turns data class with an ID and message. Create a function that accepts nullable JSON text. Return an empty list for null or blank input. Use Gson TypeToken to deserialize a List<Turns>. Demonstrate the function with sample JSON.

Take Quiz

Keep learning

Related questions

View more