Serialization Libraries Compared
Why Serialization Matters on Android
Every REST API call exchanges JSON. Serialization is the process of converting Kotlin objects to JSON (serialization) and JSON back to Kotlin objects (deserialization). The library you choose affects performance, null safety, and code generation.
Gson (Google)
Gson uses reflection at runtime. No code generation. Works out of the box.
// build.gradle.kts
implementation("com.google.code.gson:gson:2.11.0")
data class User(
val id: Long,
val name: String,
val email: String,
val age: Int?
)
// Serialize
val user = User(1, "Alice", "alice@example.com", 30)
val json = Gson().toJson(user)
// Deserialize
val parsed = Gson().fromJson(json, User::class.java)
Problem: Gson does not distinguish between missing keys and null values. A missing age field deserializes as null, but so does "age": null. This causes subtle bugs when the server omits a field vs sends null.
Moshi (Square)
Moshi uses reflection but with better null safety. It also supports code generation via @JsonClass(generateAdapter = true).
// build.gradle.kts
implementation("com.squareup.moshi:moshi-kotlin:1.15.1")
implementation("com.squareup.moshi:moshi-kotlin-codegen:1.15.1")
@JsonClass(generateAdapter = true)
data class User(
val id: Long,
val name: String,
val email: String,
val age: Int? = null
)
val moshi = Moshi.Builder()
.addLast(KotlinJsonAdapterFactory())
.build()
val adapter = moshi.adapter(User::class.java)
val json = adapter.toJson(user)
val parsed = adapter.fromJson(json)
Moshi throws on unknown keys by default (fail-on-unknown). This catches API schema drift early.
kotlinx.serialization
A Kotlin-first library with compile-time code generation. No reflection. Best performance.
// build.gradle.kts
plugin("org.jetbrains.kotlin.plugin.serialization")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
@Serializable
data class User(
val id: Long,
val name: String,
val email: String,
@SerialName("age") val age: Int? = null
)
val json = Json {
ignoreUnknownKeys = true
coerceInputValues = true
}
val serialized = json.encodeToString(user)
val parsed = json.decodeFromString<User>(json)
coerceInputValues = true coerces null values from JSON to default values in Kotlin. ignoreUnknownKeys = true prevents crashes when the server adds new fields.
Handling Nullable Fields and Defaults
The Nullable Field Problem
APIs often omit fields rather than sending null. Your data class must handle both cases:
// Server returns {"id": 1, "name": "Alice"} without email or age
// Gson: age and email will be null (same as {"email": null})
// Moshi: age and email will use Kotlin defaults if defined
// kotlinx.serialization: use coerceInputValues = true
Recommended Pattern
@Serializable
data class User(
val id: Long,
val name: String,
val email: String = "", // Default for missing field
val age: Int? = null, // Nullable with default
val metadata: Map<String, String> = emptyMap() // Empty map default
)
Always provide defaults for optional fields. This prevents crashes when the server evolves its schema.
Custom Serializers for Complex Types
For types the library cannot handle automatically:
// kotlinx.serialization custom serializer for Instant
class InstantSerializer : KSerializer<Instant> {
override val descriptor = PrimitiveSerialDescriptor("Instant", PrimitiveKind.STRING)
override fun serialize(encoder: Encoder, value: Instant) {
encoder.encodeString(value.toString())
}
override fun deserialize(decoder: Decoder): Instant {
return Instant.parse(decoder.decodeString())
}
}
@Serializable
data class Event(
val id: Long,
@Serializable(with = InstantSerializer::class)
val timestamp: Instant
)
Polymorphic Serialization
When a field can be one of several types, use sealed classes:
@Serializable
sealed class Result {
@Serializable
data class Success(val data: User) : Result()
@Serializable
data class Error(val code: Int, val message: String) : Result()
}
// Use JsonContentPolymorphicAdapter with Moshi
// or @JsonClassDiscriminator with kotlinx.serialization
Moshi Adapters vs TypeAdapters
Moshi adapters are faster than Gson TypeAdapters because they avoid boxing primitives. For performance-critical paths, write a custom JsonAdapter.Factory instead of using reflection-based adapters.
Quiz
1. What is the key difference between Gson and kotlinx.serialization?
2. What does coerceInputValues = true do in kotlinx.serialization?
3. Why should Moshi's fail-on-unknown behavior be kept enabled?
4. Which serialization library requires a Kotlin compiler plugin?
Flashcards
Question
What is the difference between serialization and deserialization?
Click to reveal answer
Answer
Serialization converts Kotlin objects to JSON strings for sending to a server. Deserialization parses JSON strings from a server back into Kotlin objects.
Question
Why is kotlinx.serialization preferred over Gson for new Android projects?
Click to reveal answer
Answer
It uses compile-time code generation instead of reflection, resulting in smaller APKs, faster serialization, and better compatibility with R8/ProGuard. It also integrates natively with Kotlin.
Question
What does ignoreUnknownKeys = true do?
Click to reveal answer
Answer
It tells the deserializer to silently skip JSON fields that have no corresponding property in the data class. Without it, an unknown field causes a serialization exception.
Question
How do you handle polymorphic types in JSON serialization?
Click to reveal answer
Answer
Use sealed classes with type discriminators. kotlinx.serialization uses @JsonClassDiscriminator, Moshi uses JsonContentPolymorphicAdapter, and Gson uses RuntimeTypeAdapterFactory.
Revision Notes
Key Takeaways
- 1. kotlinx.serialization offers the best performance via compile-time code generation
- 2. Always provide default values for optional fields to handle API schema changes
- 3. Use ignoreUnknownKeys and coerceInputValues for robust deserialization
- 4. Moshi's fail-on-unknown catches API drift early in development
Interview Tips
- • Explain the tradeoff between reflection and code generation for serialization
- • Discuss how you handle nullable fields across different serialization libraries
- • Describe strategies for backward-compatible API model evolution
- • Know why Gson's poor null handling can cause production crashes
Cheat Sheet
Serialization Cheat Sheet
Libraries:
- Gson: reflection-based, simple setup, poor null handling
- Moshi: reflection + codegen, better null safety, fail-on-unknown
- kotlinx.serialization: compile-time codegen, fastest, Kotlin-native
Best Practices:
- Always provide defaults for nullable/optional fields
- Use coerceInputValues = true with kotlinx.serialization
- Use @JsonClass(generateAdapter = true) with Moshi
- Keep ignoreUnknownKeys = true to handle API evolution
Null Handling:
- Server omits field -> needs default value in data class
- Server sends null -> nullable type or default
- Never use lateinit var for API models