JSON serialization and API evolution
Kotlin serialization, Moshi, and Gson differ in code generation/reflection, Kotlin null/default handling, adapters, polymorphism, and multiplatform support. Choose by contract/tooling rather than fashion and test the exact configuration.
val json = Json {
ignoreUnknownKeys = true
explicitNulls = true
isLenient = false
}
@Serializable
data class ProductDto(
val id: String,
val title: String,
val status: String? = null,
)
Missing versus explicit null
These may mean different things, especially PATCH:
{} // leave unchanged/default
{"nickname":null} // clear nickname
Some serializers/configurations collapse them. Model optional patch fields with a tri-state wrapper if API distinguishes absent/null/value.
explicitNulls=false can make encode/decode asymmetric when nullable property has non-null default. Understand behavior before changing global config.
Compatibility
- Readers generally ignore new unknown fields for forward compatibility.
- New required field breaks older payloads unless default/nullable/version strategy exists.
- Removing/renaming field needs compatibility window.
- Unknown enum value should not crash an entire feed; preserve raw or map
Unknownunless security/business requires fail-closed. - Numeric precision/overflow must be validated; JSON number into Double can corrupt money/large IDs.
Polymorphism
Use explicit discriminator and allowlist known subtypes. Never instantiate arbitrary class names from untrusted JSON. Unknown subtype needs policy: ignore item, preserve opaque payload, or fail.
Defaults can hide server regressions
val price: Long = 0 converts missing price into free product. Defaults are product policy, not parser convenience. Required safety-critical values should fail mapping.
Serialization boundaries
DTO remains at network edge. Map into validated models before DB/UI. Do not persist raw DTO JSON indefinitely without schema/version/migration/retention plan.
Test old client/new server, new client/old cached payload, missing/null/unknown/extra fields, reordered fields, overflow, malformed Unicode/date, and polymorphic unknown subtype.
Sources
Exercises
- Model absent/null/value PATCH field.
- Prevent missing price default corruption.
- Design unknown enum/subtype handling for payments and feeds.