Skip to content
Back to Blog
By JSONConvert Team··6 min read

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 typeKotlin typeNotes
stringStringNon-nullable unless the field can be null
number (integer)Int or LongUse Long for values that may exceed 2,147,483,647
number (float)DoubleUse BigDecimal for monetary amounts
booleanBooleanDirect mapping
nullT?The question mark makes the type nullable
objectdata classDefine a separate named data class
arrayListT is the element type
The JSON spec does not distinguish between integers and floating-point numbers. When a generator sees 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.

LibraryBest forKey annotation
kotlinx.serializationKotlin Multiplatform, new Android/backend projects@Serializable, @SerialName
MoshiAndroid projects using KSP codegen, no reflection@Json(name = "...")
GsonLegacy Android codebases, Java interopNone 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.

Related Tools