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.