Serialization with kotlinx.serialization
Reviewed & published by Brayan K
Apps constantly turn objects into JSON to send over the network or save to disk, and back again. kotlinx.serialization is Kotlin's official, compiler-driven library for exactly that.
Part of the free Kotlin course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
By the end you'll annotate classes with @Serializable, convert objects with Json.encodeToString and decodeFromString, and remap keys with @SerialName.
What You'll Learn in This Lesson
- What serialization is
- Marking classes with @Serializable
- Encoding objects to JSON
- Decoding JSON back to objects
- Remapping keys with @SerialName
- Serializing lists of objects
1️⃣ @Serializable, encode, and decode
Annotate a class with @Serializable, then use Json.encodeToString to turn an instance into JSON and Json.decodeFromString to parse it back.
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
// @Serializable lets kotlinx.serialization generate a serializer.
@Serializable
data class User(val name: String, val age: Int)
fun main() {
val user = User("Ada", 36)
// Object -> JSON string
val json = Json.encodeToString(user)
println(json) // {"name":"Ada","age":36}
// JSON string -> object
val back = Json.decodeFromString<User>(json)
println(back.name) // Ada
}No manual JSON building, no reflection at runtime — the compiler plugin generated a type-safe serializer for User.
2️⃣ Remapping Keys with @SerialName
When the JSON uses different key names than your Kotlin properties, @SerialName bridges the gap so your code stays idiomatic.
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
@Serializable
data class Account(
@SerialName("user_id") val userId: Int,
@SerialName("display_name") val displayName: String
)
fun main() {
val acc = Account(userId = 7, displayName = "grace")
// @SerialName maps the Kotlin property to a different JSON key.
val json = Json.encodeToString(acc)
println(json) // {"user_id":7,"display_name":"grace"}
}3️⃣ Serializing Collections
Collections of serializable types work directly — encode and decode a List<Task> with the same calls.
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
@Serializable
data class Task(val title: String, val done: Boolean)
fun main() {
val tasks = listOf(
Task("Learn serialization", true),
Task("Build an app", false)
)
// Lists of @Serializable types work directly.
val json = Json.encodeToString(tasks)
println(json)
val restored: List<Task> = Json.decodeFromString(json)
println(restored.size) // 2
}Your turn. Replace the TODO, then run and compare.
// 🎯 YOUR TURN — replace the TODO then run main().
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
// 1) Make this class serializable.
// 👉 replace TODO with the annotation: @Serializable
TODO
data class Book(val title: String, val pages: Int)
fun main() {
val book = Book("Kotlin in Action", 360)
println(Json.encodeToString(book))
// expect: {"title":"Kotlin in Action","pages":360}
}Mini-Challenge: Round-trip a Point
Encode a small object to JSON, decode it back, and prove the data survived the round trip.
// 🎯 MINI-CHALLENGE: round-trip a Point.
//
// 1. Define @Serializable data class Point(val x: Int, val y: Int).
// 2. In main(), create a Point(3, 4).
// 3. Encode it to a JSON string and print it.
// 4. Decode the string back to a Point and print point.x + point.y.
//
// ✅ Example output:
// {"x":3,"y":4}
// 7
fun main() {
// your code here
}Common Errors (and the fix)
- Missing @Serializable — Encoding a non-annotated class fails to compile. Add the annotation to the class.
- Forgetting the type on decode — Use decodeFromString<User>(json) so the library knows what to build.
- Key mismatch — If JSON keys differ from property names, add @SerialName.
- Unknown extra keys — Configure Json { ignoreUnknownKeys = true } to tolerate them.
Pro Tips
- 💡 Reuse a configured Json: create one Json { ... } instance with your settings and reuse it.
- 💡 Default values help: properties with defaults make decoding resilient to missing keys.
- 💡 Pair with Retrofit: a Json converter lets Retrofit decode responses straight into your data classes.
📋 Quick Reference — Serialization
| Concept | Kotlin |
|---|---|
| Mark class | @Serializable data class User(...) |
| Object to JSON | Json.encodeToString(user) |
| JSON to object | Json.decodeFromString<User>(json) |
| Remap key | @SerialName("user_id") val userId |
| Lenient parse | Json { ignoreUnknownKeys = true } |
🎉 Lesson Complete!
- ✅ Serialization turns objects into JSON and back
- ✅ @Serializable enables a generated serializer
- ✅ encodeToString / decodeFromString do the conversion
- ✅ @SerialName remaps JSON keys
- ✅ Collections of serializable types work directly
Practice quiz
What does serialization mean?
- Compiling code to bytecode
- Converting an object into a transferable format like JSON text
- Encrypting a password
- Running tests in order
Answer: Converting an object into a transferable format like JSON text. Serialization turns an in-memory object into a format (e.g. JSON) you can store or send.
Which annotation marks a class for kotlinx.serialization?
- @Json
- @SerializedName
- @Serializable
- @Serialize
Answer: @Serializable. @Serializable (from kotlinx.serialization) makes a class serializable.
Which call converts an object to a JSON string?
- Json.encodeToString(value)
- Json.toJson(value)
- value.serialize()
- Json.write(value)
Answer: Json.encodeToString(value). Json.encodeToString(value) produces the JSON text for a @Serializable object.
Which call parses a JSON string back into an object?
- Json.parse(text)
- Json.fromJson(text)
- Json.decodeFromString<T>(text)
- text.deserialize()
Answer: Json.decodeFromString<T>(text). Json.decodeFromString<T>(jsonString) reconstructs the object of type T.
What does @SerialName("user_id") do?
- Renames the class
- Maps a Kotlin property to a different JSON key name
- Makes the property private
- Marks it transient
Answer: Maps a Kotlin property to a different JSON key name. @SerialName customizes the JSON key for a property, e.g. userId <-> user_id.
Deserialization is the opposite of serialization. What does it produce?
- A JSON string
- An object reconstructed from the serialized data
- A compiled class
- A network request
Answer: An object reconstructed from the serialized data. Deserialization turns serialized text back into an in-memory object.
Which import provides the Json object in kotlinx.serialization?
- import org.json.JSONObject
- import com.google.gson.Gson
- import java.util.Json
- import kotlinx.serialization.json.Json
Answer: import kotlinx.serialization.json.Json. The Json entry point lives in kotlinx.serialization.json.
How do you serialize/deserialize a list of @Serializable items?
- You cannot serialize collections
- Convert each element to a string manually
- Json.encodeToString and decodeFromString work with List<T> directly
- Use a database
Answer: Json.encodeToString and decodeFromString work with List<T> directly. kotlinx.serialization supports List<T> (and other collections) of serializable types out of the box.
What is a common reason to configure Json { ignoreUnknownKeys = true }?
- To speed up the app
- To ignore JSON keys not present in your data class instead of throwing
- To encrypt the output
- To pretty-print only
Answer: To ignore JSON keys not present in your data class instead of throwing. ignoreUnknownKeys lets you tolerate extra fields in the JSON your model does not declare.
Why is kotlinx.serialization well suited to Kotlin compared to reflection-based libraries?
- It runs on the GPU
- It only works on Android
- It uses a compiler plugin for type-safe, reflection-free serialization
- It requires no annotations ever
Answer: It uses a compiler plugin for type-safe, reflection-free serialization. A compiler plugin generates serializers at build time, avoiding runtime reflection and supporting multiplatform.
Continue this course
- Previous: Android Fundamentals: Activities & Lifecycle
- Next: Networking with Retrofit — Retrofit interfaces, @GET/@POST, suspend functions and Ktor
- Quick reference: Kotlin cheat sheet
Frequently asked questions
What does @Serializable actually do?
It tells the kotlinx.serialization compiler plugin to generate a serializer for the class at build time. That generated serializer knows how to write each property to JSON (or another format) and read it back, with no runtime reflection. You add it to data classes you want to convert to and from JSON, and the library handles the rest.
How do encodeToString and decodeFromString differ?
Json.encodeToString(value) goes from object to JSON text (serialization). Json.decodeFromString<T>(jsonString) goes the other way, parsing JSON text back into an object of type T (deserialization). They're a matched pair: encode to send or store data, decode to reconstruct it. Both require the type to be @Serializable.
When do I need @SerialName?
Use @SerialName when the JSON key differs from your Kotlin property name. APIs often use snake_case (user_id) while Kotlin prefers camelCase (userId). @SerialName("user_id") on the property maps the two so your code stays idiomatic while still matching the wire format. It applies in both directions, for encoding and decoding.
Why use kotlinx.serialization over Gson or Moshi?
kotlinx.serialization is Kotlin-first: it uses a compiler plugin instead of runtime reflection, which makes it type-safe, fast, and compatible with Kotlin Multiplatform. It understands Kotlin features like default values, nullability, and sealed classes natively. Reflection-based libraries work too, but the official kotlinx option integrates most cleanly with modern Kotlin codebases.