Convert Kotlin Data Class to JSON: kotlinx.serialization vs GSON

Alexey Karimov

You have a Kotlin data class. Now you need to turn it into JSON, or read JSON back into that class. Maybe it’s for an API call, local storage, or config.

This is where serialization comes in. It means converting a Kotlin object to a JSON string. Deserialization does the reverse by turning JSON into an object your code can use.

In Kotlin, you can do this using two popular libraries: kotlinx.serialization and GSON. This guide shows how both work, where they differ, and how to handle common issues like default values and JSON key mismatches.

Note: The code examples are pseudo and can’t be executed. They are meant to help you understand the concept clearly.

Using kotlinx.serialization (Recommended for Pure Kotlin Projects)

If you’re working in a pure Kotlin project, the most idiomatic way to handle JSON is with the kotlinx.serialization library. It’s lightweight, well-integrated with Kotlin’s type system, and supports compile-time safety for both serialization and deserialization.

Step 1: Add the Library

To use kotlinx.serialization, you need to include the JSON library and apply the plugin. Without this, the compiler won’t recognize serialization-specific features. Here’s how to do it.

plugins {
    kotlin("plugin.serialization") version "x.x.x"
}

dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:x.x.x")
}

Once added, sync the project to make the library available.

Step 2: Create a Serializable Kotlin Data Class

Before you can serialize or deserialize a class, the compiler needs to know that the class is eligible for it. You do this by marking the class with @Serializable.

import kotlinx.serialization.Serializable

@Serializable
data class Product(
    val id: Int,
    val name: String
)

If you skip this annotation, Kotlin will throw a compile-time error when you try to convert the object to or from JSON.

Step 3: Convert an Object to JSON

To convert the object to a JSON string, use Json.encodeToString(). This function takes a Kotlin object and returns its JSON representation.

import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json

fun main() {
    val item = Product(101, "Mouse")
    val jsonOutput = Json.encodeToString(item)
    println(jsonOutput)
}

OUTPUT: {"id":101,"name":"Mouse"} — This can now be stored, sent over a network, or logged.

Step 4: Convert JSON Back to an Object

If you receive a JSON string and want to turn it into a Kotlin object, use Json.decodeFromString(). However, this method can throw exceptions such as SerializationException or IllegalArgumentException if the input is invalid or doesn’t match the expected structure. 

To avoid runtime crashes, it’s a good idea to wrap the decoding logic in a try block:

import kotlinx.serialization.*
import kotlinx.serialization.json.*

@Serializable
data class Product(val id: Int, val name: String)

fun main() {
    val jsonInput = """{"id":101,"name":"Mouse"}"""

    try {
        val item = Json.decodeFromString<Product>(jsonInput)
        println(item)
    } catch (e: SerializationException) {
        println("Failed to decode JSON: ${e.message}")
    } catch (e: IllegalArgumentException) {
        println("Invalid input: ${e.message}")
    }
}

OUTPUT: Product(id=101, name=Mouse) if the input is valid. In case it’s mismatched, you will find “Failed to decode JSON” or “Invalid input” messages depending on the error. This is useful when working with REST APIs, local files, or config data.

Step 5: Handle Fields with Default Values

By default, the serializer does not include properties that match their default values. This keeps the JSON output lightweight.

@Serializable
data class Product(
    val id: Int,
    val name: String = "Unknown"
)

val item = Product(102)
val jsonOutput = Json.encodeToString(item)
println(jsonOutput)

OUTPUT: {"id":102} — Here, the name is excluded because it’s set to the default “Unknown”.

Note: By default, fields with default values are not included in the JSON output. To include all default-value fields, use: val json = Json { encodeDefaults = true }. As of recent versions, field-level control via @EncodeDefault is deprecated and not recommended.”

Using GSON (For Java/Kotlin Interop)

If you’re working in a mixed Java-Kotlin environment or building for Android, GSON is a practical choice. It’s stable, well-supported, and doesn’t rely on Kotlin-specific plugins. It works out of the box using reflection and supports both single objects and collections. 

Here’s how to serialize and deserialize Kotlin data classes using GSON.

Step 1: Add the GSON Dependency

GSON isn’t included by default, so you need to add it manually. Without this, your code won’t compile when you try to call toJson() or fromJson().

implementation("com.google.code.gson:gson:x.x.x")

Sync your project to make the library available in your code.

Step 2: Define a Data Class That Matches Your JSON

GSON doesn’t require annotations to work, but it needs the property names in your Kotlin class to match the keys in your JSON. If they don’t match, deserialization will fail or produce unexpected results.

import com.google.gson.Gson

data class Task(
    val id: Int,
    val isDone: Boolean
)

This class will represent your JSON structure. Always, avoid optional types unless you handle them explicitly.

Step 3: Convert a Kotlin Object to JSON

To send data or log it, you need to turn your Kotlin object into a JSON string. GSON handles this with toJson().

val task = Task(3, false)
val gson = Gson()
val jsonOutput = gson.toJson(task)
println(jsonOutput)

This step is necessary because most APIs or storage layers require a clean, portable text format, like JSON.

OUTPUT: {"id":3,"isDone":false}

Step 4: Convert JSON Back to a Kotlin Object

If you receive data from an API or load it from a file, you’ll want to turn it into a usable Kotlin object. That’s where fromJson() comes in.

val jsonArray = """[
    {"id":1,"isDone":true},
    {"id":2,"isDone":false}
]"""

val array = gson.fromJson(jsonArray, Array<Task>::class.java)
val list = array.toList()

OUTPUT: Task(id=3, isDone=false)

This gives you a full Kotlin object with all type information preserved. If your JSON structure matches your class, this works right out of the box. 

Step 5: Convert a JSON Array to a List of Objects

APIs often return arrays of items. GSON can parse them, but Kotlin’s generic type system needs a small workaround. Instead of parsing directly into a list, parse into an array and convert it.

val jsonArray = “””[

    {“id”:1,”isDone”:true},

    {“id”:2,”isDone”:false}

]”””

val array = gson.fromJson(jsonArray, Array<Task>::class.java)

val list = array.toList()

Why not parse directly to List<Task>? Because GSON can’t infer Kotlin generics at runtime without a type token.

Handling JSON Key Mismatches

When working with external APIs or third-party data sources, it’s common for the JSON field names to differ from the property names in your Kotlin data class. 

For example, an API might return user_name, but your Kotlin class uses username. If you don’t handle this mismatch, the value won’t get mapped correctly, leaving your property uninitialized or set to its default.

With kotlinx.serialization

Use the @SerialName annotation to map a JSON key to a Kotlin property. This makes sure the username field is populated even if the JSON uses a different key.

@Serializable
data class User(
    @SerialName("user_name") val username: String
)

With GSON

GSON handles this using the @SerializedName annotation. This tells GSON to use the username property when it encounters the user_name key in the JSON.

data class User(
    @SerializedName("user_name") val username: String
)

Wrapping Up

Kotlin makes it easy to convert data classes to and from JSON. Use kotlinx.serialization for Kotlin-only projects, or GSON if you’re working with Java too. Whichever library you choose, handling defaults and mismatched keys correctly is key to avoiding subtle bugs and broken data mappings.