JSON serialization: Gson, Moshi, kotlinx.serialization
The three main libraries differ in null-safety and Kotlin support:
- Gson — the oldest, reflection-based. It is unaware of Kotlin null-safety: it can write null into a non-null
Stringfield → an NPE later, exploding somewhere unexpected. Default values are lost via reflection. - Moshi — Square's modern library: in codegen mode (
@JsonClass(generateAdapter = true)) it respects Kotlin null-safety — a null triggers a clear exception at parse time. A reflection mode also exists. - kotlinx.serialization — JetBrains' compiler-plugin solution:
@Serializable, zero reflection, multiplatform, default values fully work. An official Retrofit converter exists.
The core interview probe: "what happens when the API returns null?" Gson — a silent null in a non-null field (a hidden bomb); Moshi codegen / kotlinx — an immediate parse error (fail fast). In banking, fail-fast wins: better than continuing with corrupt data.
// Gson — annotasiya yalnız ad uyğunluğu üçün
data class TxDto(
@SerializedName("tx_id") val txId: String, // Gson null yaza bilər!
@SerializedName("amount") val amount: Long = 0 // default itir
)
// Moshi codegen — null gələrsə JsonDataException
@JsonClass(generateAdapter = true)
data class TxDto(
@Json(name = "tx_id") val txId: String,
@Json(name = "amount") val amount: Long = 0 // default işləyir
)
// kotlinx.serialization
@Serializable
data class TxDto(
@SerialName("tx_id") val txId: String,
@SerialName("amount") val amount: Long = 0 // default işləyir
)The same DTO in all three libraries
The ProGuard/R8 trap: reflection-based serialization (Gson, Moshi reflection) loses field names after obfuscation — parsing breaks in release builds while debug works. Fixes: @Keep/keep rules on DTOs, or moving to codegen modes (which need no rules). When you hear "works in debug, breaks in release", this is the first suspect.
🛠 Practice task
See the null bomb yourself: write data class UserDto(val name: String) and parse {"name": null} with Gson.
- Confirm no exception is thrown, and that the NPE only arrives at
name.length. - Parse the same JSON with Moshi codegen or kotlinx.serialization — compare where the failure surfaces.
- Parse a money field as
Doubleand print0.1 + 0.2; then redo it withLong(minor units) andBigDecimal.
Done when: you can answer "why not store money in a Double" with concrete output.
📚 Sources and documentation
- kotlinx.serialization — Kotlin docsofficialkotlinlang.org
- Moshiofficialgithub.com
- Gsonofficialgithub.com