TL;DR
使用 kotlinx.serialization 实现多平台支持,使用 Gson 简化 Android 开发,或使用 Moshi 进行基于注解的映射,将 JSON 转换为 Kotlin 数据类。数据类自动提供 equals、hashCode 和 copy() 方法。
核心要点
- Kotlin data classes auto-generate equals(), hashCode(), toString(), and copy()
- @Serializable with kotlinx.serialization is the modern multiplatform approach
- @SerialName maps JSON keys to Kotlin property names
- Nullable types (String?) handle null JSON values safely
- Default parameter values let you skip optional JSON fields
- Sealed classes model discriminated union types from JSON
- Gson's @SerializedName and Moshi's @Json serve the same purpose as @SerialName
为什么使用 Kotlin 数据类?
Kotlin data classes are purpose-built for holding structured data. Unlike Java POJOs, they eliminate hundreds of lines of boilerplate — equals(), hashCode(), toString(), and copy() are all generated by the compiler.
When deserializing JSON APIs, data classes give you compile-time type safety, IDE autocomplete, and Kotlin null safety — which prevents the null pointer exceptions that plague Java JSON code.
| Feature | Kotlin data class | Java POJO |
|---|---|---|
| equals/hashCode | Auto-generated | Manual or Lombok |
| toString() | Auto-generated | Manual or Lombok |
| copy() | Built-in | Not available |
| Destructuring | componentN() built-in | Not available |
| Null safety | Enforced by type system | Runtime NPE risk |
| Immutability | val by default | Requires discipline |
| Lines of code | 1 line | 30-50+ lines |
Basic Data Class from JSON (kotlinx.serialization)
The modern, Kotlin-first approach uses kotlinx.serialization. It is multiplatform, generates serializers at compile time (no reflection), and integrates with Kotlin null safety.
Given this JSON:
{
"id": 42,
"username": "alice",
"email": "alice@example.com",
"age": 30,
"is_active": true,
"bio": null
}The Kotlin data class with kotlinx.serialization:
import kotlinx.serialization.Serializable
import kotlinx.serialization.SerialName
@Serializable
data class User(
val id: Int,
val username: String,
val email: String,
val age: Int,
@SerialName("is_active")
val isActive: Boolean,
val bio: String? = null // nullable + default value
)
// Deserialize
val user = Json.decodeFromString<User>(jsonString)
// Serialize back to JSON
val json = Json.encodeToString(user)Add to build.gradle.kts:
plugins {
kotlin("plugin.serialization") version "1.9.22"
}
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3")
}Gson Alternative
Google\u0027s Gson is battle-tested on Android and requires zero code generation. Use it when you need a simple drop-in that works with existing Java libraries. Note that Gson uses reflection and does not understand Kotlin null safety — a null JSON field on a non-nullable Kotlin property causes a runtime crash.
import com.google.gson.annotations.SerializedName
// No @Serializable needed — Gson uses reflection
data class User(
val id: Int,
val username: String,
val email: String,
val age: Int,
@SerializedName("is_active")
val isActive: Boolean,
val bio: String? = null
)
// Setup
val gson = GsonBuilder()
.setLenient()
.create()
// Deserialize
val user = gson.fromJson(jsonString, User::class.java)
// Serialize
val json = gson.toJson(user)// build.gradle.kts
dependencies {
implementation("com.google.code.gson:gson:2.10.1")
}Moshi Alternative
Square\u0027s Moshi is designed with Kotlin in mind and supports both reflection and code generation (kapt/KSP). Unlike Gson, Moshi understands Kotlin null safety and throws JsonDataException for null mismatches instead of letting them pass silently.
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true) // triggers KSP/kapt code generation
data class User(
val id: Int,
val username: String,
val email: String,
val age: Int,
@Json(name = "is_active")
val isActive: Boolean,
val bio: String? = null
)
// Setup
val moshi = Moshi.Builder()
.addLast(KotlinJsonAdapterFactory())
.build()
val adapter = moshi.adapter(User::class.java)
// Deserialize
val user = adapter.fromJson(jsonString)
// Serialize
val json = adapter.toJson(user)// build.gradle.kts
plugins {
id("com.google.devtools.ksp") version "1.9.22-1.0.17"
}
dependencies {
implementation("com.squareup.moshi:moshi:1.15.1")
implementation("com.squareup.moshi:moshi-kotlin:1.15.1")
ksp("com.squareup.moshi:moshi-kotlin-codegen:1.15.1")
}Jackson with Kotlin
Jackson is the de-facto standard in Spring Boot projects. Add the jackson-module-kotlin to make it understand Kotlin data classes, nullable types, and default parameters.
import com.fasterxml.jackson.annotation.JsonProperty
// No special annotation needed if jackson-module-kotlin is registered
data class User(
val id: Int,
val username: String,
val email: String,
val age: Int,
@JsonProperty("is_active")
val isActive: Boolean,
val bio: String? = null
)
// Setup with Kotlin module
val mapper = ObjectMapper().apply {
registerModule(KotlinModule.Builder().build())
configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
}
// Deserialize
val user = mapper.readValue<User>(jsonString)
// Serialize
val json = mapper.writeValueAsString(user)// build.gradle.kts (Spring Boot adds these automatically)
dependencies {
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.16.1")
implementation("com.fasterxml.jackson.core:jackson-databind:2.16.1")
}Nested Objects and Lists
JSON with nested objects maps to separate data classes. Arrays map to List<T>.
// Input JSON
{
"order_id": "ORD-001",
"customer": {
"id": 42,
"name": "Alice"
},
"items": [
{ "sku": "ITEM-A", "quantity": 2, "price": 9.99 },
{ "sku": "ITEM-B", "quantity": 1, "price": 24.99 }
],
"tags": ["priority", "gift"]
}import kotlinx.serialization.Serializable
import kotlinx.serialization.SerialName
@Serializable
data class Customer(
val id: Int,
val name: String
)
@Serializable
data class OrderItem(
val sku: String,
val quantity: Int,
val price: Double
)
@Serializable
data class Order(
@SerialName("order_id") val orderId: String,
val customer: Customer,
val items: List<OrderItem>,
val tags: List<String> = emptyList()
)
// Parse entire hierarchy in one call
val order = Json.decodeFromString<Order>(jsonString)
// Access nested data
println(order.customer.name) // Alice
println(order.items[0].sku) // ITEM-A
println(order.items.sumOf { it.price * it.quantity }) // total costNullable Fields and Default Values
Kotlin\u0027s type system distinguishes nullable (String?) from non-nullable (String) types. Map JSON null values to nullable types, and use default values for optional JSON keys that may be absent entirely.
@Serializable
data class UserProfile(
val id: Int, // required, never null
val username: String, // required, never null
val bio: String? = null, // JSON null or absent -> null
val avatarUrl: String? = null, // JSON null or absent -> null
val followersCount: Int = 0, // absent in JSON -> defaults to 0
val isVerified: Boolean = false, // absent in JSON -> defaults to false
val roles: List<String> = emptyList(), // absent in JSON -> empty list
val metadata: Map<String, String> = emptyMap()
)
// Lenient parsing — ignores extra keys, uses defaults for missing keys
val json = Json {
ignoreUnknownKeys = true
coerceInputValues = true // coerces invalid values to defaults
}
// Null-safe access
val profile = json.decodeFromString<UserProfile>(jsonString)
val displayBio = profile.bio ?: "No bio provided"
profile.avatarUrl?.let { loadAvatar(it) } // safe callWith ignoreUnknownKeys = true, the parser silently ignores JSON fields that have no matching property in the data class — essential for robust API clients that must handle evolving APIs.
Enum Handling
JSON string values that represent a finite set of options map naturally to Kotlin enums. Both kotlinx.serialization and Gson support enum serialization out of the box.
import kotlinx.serialization.SerialName
@Serializable
enum class OrderStatus {
@SerialName("pending") PENDING,
@SerialName("processing") PROCESSING,
@SerialName("shipped") SHIPPED,
@SerialName("delivered") DELIVERED,
@SerialName("cancelled") CANCELLED,
}
@Serializable
data class Order(
val id: String,
val status: OrderStatus, // maps JSON "shipped" to OrderStatus.SHIPPED
val amount: Double
)
// Usage
val order = Json.decodeFromString<Order>("""
{"id": "ORD-1", "status": "shipped", "amount": 49.99}
""")
println(order.status) // SHIPPED
// Unknown enum values — use lenient mode or provide a fallback
val lenientJson = Json {
ignoreUnknownKeys = true
isLenient = true
}
// With Gson — enums serialize by name automatically
val gson = Gson()
val json = gson.toJson(OrderStatus.SHIPPED) // "SHIPPED"Date/Time with kotlinx.serialization
kotlinx-datetime provides multiplatform date/time classes. Use Instant for timestamps (ISO 8601), LocalDate for dates without time.
import kotlinx.datetime.Instant
import kotlinx.datetime.LocalDate
import kotlinx.datetime.serializers.InstantIso8601Serializer
@Serializable
data class Event(
val id: Int,
val title: String,
// ISO 8601 string in JSON: "2026-02-27T10:30:00Z"
@Serializable(with = InstantIso8601Serializer::class)
val createdAt: Instant,
// Date string in JSON: "2026-03-01"
val eventDate: LocalDate,
)
// build.gradle.kts
// implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.5.0")
// For JVM-only (Gson/Jackson) with java.time
data class EventJvm(
val id: Int,
@JsonAdapter(InstantAdapter::class) // Gson custom adapter
val createdAt: java.time.Instant,
)
// Custom Gson TypeAdapter for Instant
class InstantAdapter : TypeAdapter<java.time.Instant>() {
override fun write(out: JsonWriter, value: java.time.Instant) {
out.value(value.toString())
}
override fun read(`in`: JsonReader): java.time.Instant {
return java.time.Instant.parse(`in`.nextString())
}
}Sealed Classes for Union Types
Sealed classes model JSON payloads that have different shapes based on a discriminator field. kotlinx.serialization supports polymorphism through SerializersModule.
// JSON variants:
// {"type": "card", "last4": "1234", "brand": "Visa"}
// {"type": "paypal", "email": "user@example.com"}
// {"type": "bank", "account_number": "****5678"}
@Serializable
sealed class PaymentMethod {
abstract val type: String
}
@Serializable
@SerialName("card")
data class CardPayment(
override val type: String = "card",
val last4: String,
val brand: String
) : PaymentMethod()
@Serializable
@SerialName("paypal")
data class PayPalPayment(
override val type: String = "paypal",
val email: String
) : PaymentMethod()
@Serializable
@SerialName("bank")
data class BankPayment(
override val type: String = "bank",
@SerialName("account_number") val accountNumber: String
) : PaymentMethod()
// Configure polymorphic serialization
val json = Json {
serializersModule = SerializersModule {
polymorphic(PaymentMethod::class) {
subclass(CardPayment::class)
subclass(PayPalPayment::class)
subclass(BankPayment::class)
}
}
}
// Parse — Kotlin when expression gives exhaustive handling
val payment = json.decodeFromString<PaymentMethod>(jsonString)
when (payment) {
is CardPayment -> println("Card: ${payment.brand} *${payment.last4}")
is PayPalPayment -> println("PayPal: ${payment.email}")
is BankPayment -> println("Bank: ${payment.accountNumber}")
}Coroutines + Ktor Client Integration
Ktor is the Kotlin-native HTTP client that pairs naturally with kotlinx.serialization. It handles JSON content negotiation, request/response serialization, and coroutine integration.
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.engine.cio.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.request.*
import io.ktor.serialization.kotlinx.json.*
@Serializable
data class GitHubUser(
val login: String,
val id: Int,
val name: String?,
@SerialName("public_repos") val publicRepos: Int,
val bio: String? = null
)
// Create Ktor client with kotlinx.serialization
val client = HttpClient(CIO) {
install(ContentNegotiation) {
json(Json {
ignoreUnknownKeys = true
isLenient = true
})
}
}
// Suspend function — call from coroutine scope
suspend fun fetchUser(username: String): GitHubUser {
return client.get("https://api.github.com/users/$username") {
header("Accept", "application/vnd.github.v3+json")
}.body()
}
// Usage in coroutine
viewModelScope.launch {
val user = fetchUser("octocat")
println(user.name) // The Octocat
println(user.publicRepos) // number of public repos
}
// POST with serialized body
suspend fun createUser(user: User): User {
return client.post("https://api.example.com/users") {
contentType(ContentType.Application.Json)
setBody(user)
}.body()
}// build.gradle.kts
dependencies {
implementation("io.ktor:ktor-client-core:2.3.9")
implementation("io.ktor:ktor-client-cio:2.3.9")
implementation("io.ktor:ktor-client-content-negotiation:2.3.9")
implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.9")
}Android / Retrofit Integration
Retrofit is the dominant Android HTTP client. Combine it with either Gson (traditional) or kotlinx.serialization (modern) converter factories.
// Data classes (same structure works with both converters)
@Serializable
data class Post(
val id: Int,
val title: String,
val body: String,
@SerialName("user_id") val userId: Int
)
// Retrofit interface
interface PostsApi {
@GET("posts")
suspend fun getPosts(): List<Post>
@GET("posts/{id}")
suspend fun getPost(@Path("id") id: Int): Post
@POST("posts")
suspend fun createPost(@Body post: Post): Post
}
// Option A: kotlinx.serialization converter (recommended)
val retrofit = Retrofit.Builder()
.baseUrl("https://jsonplaceholder.typicode.com/")
.addConverterFactory(
Json { ignoreUnknownKeys = true }
.asConverterFactory("application/json".toMediaType())
)
.build()
// Option B: Gson converter (simpler setup)
val retrofit = Retrofit.Builder()
.baseUrl("https://jsonplaceholder.typicode.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
val api = retrofit.create(PostsApi::class.java)
// Usage in ViewModel
viewModelScope.launch {
try {
val posts = api.getPosts() // returns List<Post>
_uiState.value = UiState.Success(posts)
} catch (e: Exception) {
_uiState.value = UiState.Error(e.message ?: "Unknown error")
}
}// build.gradle.kts (Android)
dependencies {
// Retrofit
implementation("com.squareup.retrofit2:retrofit:2.9.0")
// Option A: kotlinx.serialization converter
implementation("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3")
// Option B: Gson converter
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
// Coroutines for Android
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
}Tools for JSON to Kotlin Conversion
Several tools automate Kotlin data class generation from JSON — saving time and preventing typos:
| Tool | Best for | Library support |
|---|---|---|
| DevToolBox JSON to Kotlin | Online, quick use | kotlinx.serialization, Gson, Moshi |
| IntelliJ IDEA plugin | IDE integration | Gson, Jackson, kotlinx |
| JsonToKotlinClass plugin | Complex nested JSON | All major libraries |
| json-to-kotlin.com | Browser-based | kotlinx.serialization |
| Moshi Kotlin codegen | Production Android | Moshi only |
The DevToolBox JSON to Kotlin converter supports all three major libraries — paste your JSON and instantly get annotated data classes with proper @SerialName, @SerializedName, or @Json(name) annotations for snake_case fields.
Common Pitfalls
Avoid these common mistakes when working with JSON and Kotlin data classes:
Library Comparison Summary
| Criterion | kotlinx.serialization | Gson | Moshi | Jackson |
|---|---|---|---|---|
| Multiplatform (KMP) | Yes | JVM only | JVM only | JVM only |
| Kotlin null safety | Full support | Partial | Full support | With module |
| Reflection | No (compile-time) | Yes | Optional (KSP) | Yes |
| Setup complexity | Medium | Simple | Medium | Medium |
| Spring Boot | Supported | Supported | Supported | Default |
| Android Retrofit | Supported | Traditional | Supported | Supported |
| Performance | Excellent | Good | Excellent | Good |
| Recommendation | New KMP/Ktor projects | Simple Android | Android with codegen | Spring Boot |
常见问题
Ready to convert your JSON to Kotlin data classes?
立即使用免费的 JSON 转 Kotlin 工具 →