How to Convert JSON to Kotlin Data Class (kotlinx Guide)
Need a JSON to Kotlin data class generator that just works? Here's the workflow. Your Android app calls a REST endpoint with Retrofit or Ktor and comes back with JSON. You need a Kotlin data class to deserialize it — typed, immutable, idiomatic. Hand-typing the class from the docs example is fragile and slow. Generate it from a real payload instead.
In this guide you'll learn how to convert JSON to a Kotlin data class using PDFFlare's JSON to Kotlin tool — how kotlinx.serialization's @SerialName annotation maps JSON keys onto camelCase properties, why val is the right default, how to swap to Moshi or Gson, and the gotchas that show up at runtime.
Why kotlinx.serialization?
kotlinx.serialization is JetBrains' first-party serialization library for Kotlin — multiplatform, fast, and built for data classes. Compared to Moshi or Gson it has zero reflection overhead (the compiler plugin generates serializers at build time) and supports Kotlin/JS, Kotlin/Native, and the JVM equally well.
How to Convert JSON to a Kotlin Data Class (Step by Step)
- Open PDFFlare's JSON to Kotlin tool.
- Paste your JSON. A real production payload — sample data may miss fields the live API includes.
- Set the root class name if
Rootdoesn't fit your domain.UserResponse,OrderEvent,FirebaseUser— pick the noun. - Click Convert to Kotlin. The tool emits one
data classper unique object shape, with@SerialNameannotations preserving original keys and@Serializableat the class level. - Drop into your project. Add the kotlinx-serialization plugin to your Gradle build, and
Json.decodeFromString<UserResponse>(jsonString)returns a typed instance.
Real Use Cases for JSON to Kotlin
Android API integrations (Retrofit, Ktor)
Both libraries support kotlinx.serialization out of the box. Your API client becomes suspend fun getUser(): User — typed all the way down.
Firebase / Firestore documents
Firestore documents are JSON-shaped. Generate the data class once, annotate for Firestore deserialization, and you have typed snapshots throughout your app.
Kotlin Multiplatform projects
Share data classes between Android, iOS, and JS targets. Generate once from a JSON sample and reuse the same class on every platform.
Migrating Java to Kotlin
Don't hand-port the POJOs. Paste the JSON, generate fresh Kotlin data classes, and drop them into the converted file tree.
Common Mistakes (and How to Avoid Them)
- Forgetting the kotlinx-serialization plugin. Just adding the runtime dependency isn't enough — the plugin generates the serializer at compile time. Add it to
build.gradle.kts. - Treating every field as required.The generator doesn't add defaults. For optional fields, refine to
String?or add= nulldefault values. - Using val for fields you mutate. Switch to
varfor any field you assign after construction. - Mixing serialization libraries.kotlinx annotations don't do anything in Moshi or Gson — pick one stack and stick with it.
Privacy: Your JSON Stays Local
Conversion runs in your browser. The JSON never uploads — safe for internal API contracts, production payloads, and anything containing tokens or PII.
Related Workflows in the JSON Suite
Adjacent tools you might find useful while working on the same JSON document: the JSON to Swift and JSON to Java POJO both pair well with the conversion above. The first handles a different output format that consumers of your data may prefer; the second covers the validation side of the same workflow.
Related Tools
- JSON to Swift Codable — matching iOS classes for the same payload.
- JSON to Java POJO — for legacy JVM modules that aren't Kotlin yet.
- JSON to TypeScript — for the front end of the same product.
- JSON Formatter — pretty-print the sample before converting.
Workflow Tips for Android Teams
Working with generated Kotlin data classes day to day, a few workflow habits compound into significant productivity gains for an Android or Ktor team. The first is to keep your generated classes in a dedicated package, separate from your domain model. The generator produces a faithful representation of the wire format, which often does not match the model your application logic wants to work with. Treat the generated classes as a transport layer that you map to and from richer domain types in a service layer. This decoupling means future API changes only ripple through the transport layer, not your entire app.
A related habit is to write a tiny mapper object alongside each generated class. The mapper contains pure functions that convert from transport types to domain types. When the API changes a field name, you update the mapper and the regenerated transport class — your domain code stays unchanged. This pattern feels like extra ceremony for tiny payloads, but pays off enormously the first time an upstream team renames a field without notice. The compiler catches the mismatch at the boundary, your domain logic is untouched, and the fix is mechanical.
For Android specifically, keep an eye on R8 and ProGuard rules. kotlinx.serialization needs the data class to retain its @Serializable annotation in the obfuscated build. Most modern setups handle this automatically through the Kotlin Gradle plugin, but if you ship a release build and suddenly serialization fails, the obfuscator is the most likely culprit. Add explicit keep rules for your serialized model package as a defensive measure. The same advice applies to ProGuard configurations in any JVM project that ships minified bytecode.
Cross-team contracts deserve the same care. If your Android app talks to a backend service owned by a different team, export your generated transport classes as a versioned contract — either a small library that both projects depend on, or a snapshot in version control that both teams agree to update together. Without this discipline, the two sides drift and integration bugs become detective work. With it, you have a single source of truth that the compiler enforces.
Production Patterns for JSON to Kotlin Data Classes
Refinements that turn a generated data class into the kind of code you'd ship in a real Android app or Ktor service:
Nullability and Optional Fields
The generator can't infer optionality from a single sample. If a field might be absent in real payloads, mark it nullable by appending ? and defaulting to null: val nickname: String? = null. kotlinx.serialization handles missing keys gracefully and your call sites get safe-call syntax instead of surprise null pointer exceptions in production.
Default Values for Forward Compatibility
Adding sensible defaults to every field makes the class robust against API additions. New fields that show up in payloads but aren't in your model fall through harmlessly; missing fields get defaults instead of throwing. Configure your Json instance with encodeDefaults = falseso unchanged defaults don't bloat the serialized output.
Date and Timestamp Handling
ISO-8601 strings start as String in the generated class, then upgrade to kotlinx.datetime.Instant with a custom serializer once the field set stabilizes. Unix timestamps stay as Long and convert at the boundary via Instant.fromEpochSeconds(). Don't let raw string dates leak into your domain layer or you'll re-parse them in a dozen places.
When to Reach for a Different Approach
Kotlin data class generation is the right tool for one or two payloads. Some alternatives:
- OpenAPI codegen — when the upstream service publishes a spec, generate from that for full coverage.
- Working in Swift too? JSON to Swift emits Codable structs for iOS/macOS clients consuming the same JSON.
- For schema validation, generate a JSON Schema from your sample first, refine the constraints, then point a schema-to-Kotlin generator at it.
Common Mistakes to Avoid
- Generating from a single sparse sample.If your JSON only includes a subset of optional fields, the generator won't see the others. Capture two or three diverse payloads (a minimal one, a full-detail one, an edge case) and merge by hand. Fields appearing in some samples but not others are your nullable candidates.
- Using
varinstead ofval. The generator emitsvalby default for immutability — that's the right Kotlin idiom. Wanting to mutate after deserialization usually means mixing concerns; keep the data class immutable and create variations via.copy()instead. - Forgetting
@SerialNameon non-camelCase keys. If a JSON key isuser_id, rename the property touserIdin your code and preserve the original JSON key with@SerialName("user_id"). The generator already emits this annotation; just polish the property name. - Skipping validation. kotlinx.serialization decodes a payload missing required fields into a class with defaults — silently. For business-critical fields, add
require(field.isNotEmpty())in aninitblock. Fails fast on bad data, beats debugging mysterious behavior weeks later. - Mixing kotlinx and Moshi annotations. The generator emits kotlinx.serialization annotations. If your project uses Moshi, find-and-replace with
@JsonClassand@Json(name = "...")and swap the imports. Class structure works unchanged.
Real-World Use Cases
- Android app talking to a REST API.Generate data classes for each endpoint's response, drop into your Retrofit interface, and the network layer is type-safe end to end. Compile-time safety beats runtime crashes when you ship to thousands of devices.
- Ktor backend handling webhook payloads. Stripe, GitHub, and similar services document webhooks as JSON examples. Generate Kotlin classes from each example and your handler is compile-time safe instead of casting from a
Map<String, Any?>. - Multiplatform shared models. kotlinx works across JVM, JS, and native. Generate data classes once, share between your Android client and Kotlin/JS web client — one source of truth.
- Replacing Gson's reflection-based parsing. Migrating off Gson? The generator emits classes ready for kotlinx.serialization (3-5× faster, no reflection, R8-friendly). Paste, swap the parser call, ship.
Wrapping Up
Hand-writing a Kotlin data class from a JSON sample is fifteen minutes of boilerplate that ages with the API. Generate from a real payload with PDFFlare's JSON to Kotlin tool and refine where the inference can't guess — optionals, defaults, custom serializers.