JSON & kotlinx.serialization
Every response TrailLog's backend sends back arrives as raw JSON text over the wire — and every
Trail object TrailApiService submits has to go the other way, from a
Kotlin object back into JSON. The earlier Retrofit lesson glossed over exactly how that conversion
happens; this lesson fills in that gap.
Three Common Choices for JSON Conversion on Android
There isn't one single official JSON library for Android — three have historically been in wide use, each with a different approach:
| Library | Approach |
|---|---|
kotlinx.serialization | JetBrains' own official Kotlin library; generates conversion code at compile time via a compiler plugin, no reflection involved |
| Moshi | Square's library; also Kotlin-friendly, with an optional code-generation mode similar in spirit to kotlinx.serialization |
| Gson | the older, historically most widely-used choice; works by reading class structure via reflection at runtime |
Why kotlinx.serialization for New Kotlin Projects
For a brand-new Kotlin project like TrailLog, kotlinx.serialization is the
recommended default. Two reasons stand out. First, it's maintained by the Kotlin language team
itself, so it tracks new Kotlin language features (like sealed classes and value classes) closely,
rather than needing to catch up to them after the fact the way a third-party library might. Second,
because it generates serialization code at compile time instead of relying on reflection, it
integrates cleanly with Kotlin's own type system — null safety is respected exactly as declared, and
you get compile-time errors for a type it can't serialize, instead of a runtime crash from a
reflection-based library discovering the same problem only when the code actually runs.
Marking Trail as @Serializable
The @Serializable annotation is all that's needed to make a class eligible for
automatic JSON conversion:
@Serializable
data class Trail(
val id: Long,
val name: String,
val notes: String,
val photoUri: String?,
val latitude: Double,
val longitude: Double,
val distanceKm: Double,
val date: Long
)
With that one annotation added, plugging Retrofit's kotlinx-serialization-converter
artifact in as the converter factory (as shown in the previous two lessons) means every
TrailApiService call automatically serializes and deserializes Trail
objects — nothing manual to write for the conversion itself.
Custom Field Names With @SerialName
Backend JSON is often written in snake_case, while Kotlin convention is
camelCase. Say TrailLog's API actually returns distance_km rather than
distanceKm — @SerialName maps between the two explicitly, so the Kotlin
property can keep its idiomatic name:
@Serializable
data class Trail(
val id: Long,
val name: String,
val notes: String,
val photoUri: String?,
val latitude: Double,
val longitude: Double,
@SerialName("distance_km") val distanceKm: Double,
val date: Long
)
The JSON key stays exactly as the backend sends it — "distance_km" — while every line
of Kotlin code that touches this class keeps referring to trail.distanceKm, matching
the rest of the app's naming style.
Handling a Nested JSON Object
Suppose TrailLog's backend groups coordinates under a nested location object instead
of flat latitude/longitude fields:
{
"id": 42,
"name": "Bear Ridge Loop",
"location": { "lat": 37.4275, "lng": -122.1697 },
"distance_km": 5.2
}
A nested JSON object like this is modeled as its own small @Serializable data class,
referenced as a property type on the outer class:
@Serializable
data class TrailLocation(
@SerialName("lat") val latitude: Double,
@SerialName("lng") val longitude: Double
)
@Serializable
data class TrailResponse(
val id: Long,
val name: String,
val location: TrailLocation,
@SerialName("distance_km") val distanceKm: Double
)
kotlinx.serialization walks into the nested object automatically because
TrailLocation is itself marked @Serializable — no manual unwrapping code
is needed anywhere. A mapping function elsewhere in the repository can then flatten
TrailResponse into the app's own flat Trail data class, if that's the
shape the rest of the app expects to work with.