JSON to Kotlin Data Classes
You have an API response in JSON and you need Kotlin data classes to parse it. Writing them by hand means converting snake_case keys to camelCase Kotlin properties, adding @SerialName for every mismatch, deciding which fields can be null, and defining sub-classes for nested objects. A response with 12 fields and two nested objects becomes 60+ lines of boilerplate before you write any real logic.
Use a generator to produce the scaffold, then refine it.
JSON Types to Kotlin Types
The mapping from JSON primitives to Kotlin types is mostly one-to-one, with a few decisions that matter.
| JSON type | Kotlin type | Notes |
|---|---|---|
string | String | Non-nullable unless the field can be null |
number (integer) | Int or Long | Use Long for values that may exceed 2,147,483,647 |
number (float) | Double | Use BigDecimal for monetary amounts |
boolean | Boolean | Direct mapping |
null | T? | The question mark makes the type nullable |
object | data class | Define a separate named data class |
array | List | T is the element type |
42, it infers Int. When it sees 42.5, it infers Double. If the field can vary between integer and float depending on context, check the API documentation rather than trusting a single example payload.
A Real Example: GitHub Repository API
Here is a simplified response from the GitHub repositories endpoint:
Paste this into the JSON to Kotlin converter and you get:
Three things to notice. First, description, language, and homepage are nullable (String?) because the API can return null for them. Second, full_name, stargazers_count, and avatar_url use @SerialName because the JSON keys use snake_case while Kotlin convention uses camelCase. Third, the nested owner object becomes its own data class.
kotlinx.serialization vs Moshi vs Gson
Kotlin has three mainstream JSON libraries. The right choice depends on your environment.
| Library | Best for | Key annotation |
|---|---|---|
kotlinx.serialization | Kotlin Multiplatform, new Android/backend projects | @Serializable, @SerialName |
| Moshi | Android projects using KSP codegen, no reflection | @Json(name = "...") |
| Gson | Legacy Android codebases, Java interop | None required (uses reflection) |
kotlinx.serialization is the Kotlin team's official library and works across JVM, Android, iOS via KMP, and JavaScript targets. If you are starting a new project, use it.
Moshi avoids reflection, which matters for Android projects using R8 or ProGuard minification. Reflection-based libraries can strip serialization metadata during the minification pass, causing crashes at runtime that don't appear during development.
Gson is the oldest of the three. It works without any annotations, which sounds convenient, but the lack of explicit mapping means less control over serialization behavior. If you are already using Gson, you can reuse the generated data classes - just remove the @Serializable annotations.
Nullable Fields: Don't Guess
The most common mistake when writing Kotlin data classes from JSON is getting nullability wrong. Mark a field as non-nullable (String) when the API can return null, and you get a runtime exception - not a compile error. The bug only surfaces in production when that edge case fires.
The safe approach: treat any field that appears as null in your example payloads as nullable. For fields that look non-null, check the API documentation. If documentation is not available, test against multiple real responses in different states.
For optional fields that may be absent from the JSON entirely (rather than present with null), both kotlinx.serialization and Moshi handle this with default values:
If the key is absent and you don't provide a default value, deserialization throws a MissingFieldException. If the key is present with null as the value, you need String? - a default value alone is not enough.
Arrays of Objects
When your JSON has an array of objects, the generator produces a List with an inner data class. Here is a GitHub search response:
Generated:
language is String? because one array element has it as null. A good generator inspects all array elements to infer nullability, not just the first one. If your example payload only has one element, cross-check with the API spec before assuming the field is always non-null.
Using the Generated Classes
With kotlinx.serialization and the Kotlin serialization Gradle plugin configured:
ignoreUnknownKeys = true matters in production. APIs add fields over time. Without this setting, your app crashes when the server adds a new JSON field that your data class does not define.
If you are using Ktor, the JSON plugin wraps kotlinx.serialization directly:
The private Keyword Gotcha
The field "private": false in the GitHub example generates val \private\: Boolean because private is a reserved keyword in Kotlin. Backtick-escaped properties work but are awkward to reference. Rename the field manually after generating:
This kind of refinement is what you do after the generator handles the boilerplate. The generator gets you 90% there; you handle the edge cases specific to your codebase and naming conventions.
Format your JSON with the JSON formatter before converting if the payload is minified or hard to read, and run it through the JSON validator to catch syntax errors before they appear as confusing deserialization failures.