Kotlinx.serialization 项目集成

Gradle 安装

Android / JVM(Kotlin DSL)

Groovy 复制代码
// app/build.gradle.kts
plugins {
    id("org.jetbrains.kotlin.android")
    id("org.jetbrains.kotlin.plugin.serialization") // ← 必须
}

dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3") // 版本示例
}

Kotlin Multiplatform

Groovy 复制代码
// build.gradle.kts (KMP 根 module)
plugins {
    kotlin("multiplatform")
    id("org.jetbrains.kotlin.plugin.serialization")
}

kotlin {
    androidTarget()
    jvm()
    ios()
    sourceSets {
        val commonMain by getting {
            dependencies {
                implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3")
            }
        }
    }
}

基础用法(最常用 3 行)

Kotlin 复制代码
import kotlinx.serialization.*
import kotlinx.serialization.json.*

@Serializable
data class User(
    val id: Long,
    val name: String,
    val email: String? = null,  // 可空 & 有默认值
)

val json = Json { ignoreUnknownKeys = true } // 忽略后端多给的字段

val u = User(1, "Alice")
val s: String = json.encodeToString(u)                // 对象 → JSON 字符串
val u2: User = json.decodeFromString<User>(s)         // JSON 字符串 → 对象

集合、嵌套、枚举

Kotlin 复制代码
@Serializable
enum class Role { ADMIN, USER, GUEST }

@Serializable
data class Team(
    val name: String,
    val members: List<User>,
    val tags: Set<String> = emptySet(),
    val roles: Map<Long, Role> = emptyMap()
)

Json 配置常用选项

Kotlin 复制代码
val json = Json {
    ignoreUnknownKeys = true      // 后端多给字段也不报错
    encodeDefaults = true         // 输出默认值;关掉可减小体积
    prettyPrint = false           // 日常关;调试可开
    isLenient = true              // 宽松模式,允许非标准 JSON
    allowTrailingComma = true
    explicitNulls = false         // null 字段不主动输出
    // classDiscriminator = "type" // 多态时的类型标记字段名(见下)
    // namingStrategy = JsonNamingStrategy.SnakeCase // 字段命名转换(版本要求较新)
}

字段改名、别名、跳过

Kotlin 复制代码
@Serializable
data class Article(
    @SerialName("id") val articleId: String,      // 接口字段叫 id,本地想叫 articleId
    @SerialName("title") val title: String,
    @JsonNames("cover", "thumbnail") val image: String? = null, // 兼容多个可能的字段名
    @Transient val cacheOnly: Boolean = false // 仅本地使用,不参与序列化
)

多态 & sealed class(后端返回不同"类型"的统一字段)

推荐 sealed class(编译期可知子类):

Kotlin 复制代码
@Serializable
sealed class Msg {
    abstract val id: String
}

@Serializable
@SerialName("text")
data class TextMsg(override val id: String, val text: String): Msg()

@Serializable
@SerialName("image")
data class ImageMsg(override val id: String, val url: String): Msg()

Json 配置 + 解码:

Kotlin 复制代码
val json = Json {
    classDiscriminator = "type" // 后端会给 {"type":"text", ...}
    ignoreUnknownKeys = true
}

val msg: Msg = json.decodeFromString("""{"type":"text","id":"1","text":"hi"}""")
when (msg) {
    is TextMsg -> println(msg.text)
    is ImageMsg -> println(msg.url)
}

如果是"开放层级"多态(不是 sealed,或子类在别处定义),需要注册:

Kotlin 复制代码
val json = Json {
    ignoreUnknownKeys = true
    classDiscriminator = "type"
    serializersModule = SerializersModule {
        polymorphic(Msg::class) {
            subclass(TextMsg::class, TextMsg.serializer())
            subclass(ImageMsg::class, ImageMsg.serializer())
        }
    }
}

自定义类型 / 自定义序列化器

Groovy 复制代码
dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.1")
}
Kotlin 复制代码
@Serializable
data class Event(
    val id: String,
    val at: kotlinx.datetime.Instant // 直接用
)

完全自定义(例如 UUID 或特殊格式)

Kotlin 复制代码
import kotlinx.serialization.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*
import java.util.UUID

@Serializer(forClass = UUID::class)
object UUIDAsString : KSerializer<UUID> {
    override val descriptor: SerialDescriptor =
        PrimitiveSerialDescriptor("UUIDAsString", PrimitiveKind.STRING)

    override fun serialize(encoder: Encoder, value: UUID) {
        encoder.encodeString(value.toString())
    }
    override fun deserialize(decoder: Decoder): UUID =
        UUID.fromString(decoder.decodeString())
}

@Serializable
data class WithUuid(
    @Serializable(with = UUIDAsString::class)
    val id: UUID
)

错误处理(生产可用模板)

Kotlin 复制代码
import kotlinx.serialization.SerializationException
import kotlinx.serialization.json.Json

inline fun <reified T> safeDecode(
    jsonString: String,
    json: Json = Json { ignoreUnknownKeys = true },
    onError: (String) -> Unit = {}
): T? {
    return try {
        json.decodeFromString<T>(jsonString)
    } catch (e: SerializationException) {
        onError("解析失败: ${e.message}\n片段: ${jsonString.take(300)}")
        null
    }
}

和 Ktor / Retrofit 集成

Ktor Client

Groovy 复制代码
implementation("io.ktor:ktor-client-core:2.3.7")
implementation("io.ktor:ktor-client-android:2.3.7")
implementation("io.ktor:ktor-client-content-negotiation:2.3.7")
implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.7")
Kotlin 复制代码
val client = HttpClient(Android) {
    install(ContentNegotiation) {
        json(Json { ignoreUnknownKeys = true })
    }
}
Kotlin 复制代码
@Serializable data class Todo(val id: Int, val title: String)
val todo: Todo = client.get("https://example.com/todo/1").body()

Retrofit(用 Kotlinx 的 Converter)

Groovy 复制代码
implementation("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0")
implementation("com.squareup.retrofit2:retrofit:2.11.0")
Kotlin 复制代码
val json = Json { ignoreUnknownKeys = true }
val retrofit = Retrofit.Builder()
    .baseUrl("https://api.example.com/")
    .addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
    .build()

与 Gson 的关键差异(迁移要点)

  • 无反射 :kotlinx 在编译期生成 $$serializer,启动更快、可裁剪。

  • 默认值处理encodeDefaults 决定是否输出默认值;Gson 默认不输出 transient 字段,kotlinx 用 @Transient

  • 字段名 :用 @SerialName@JsonNames;或全局 namingStrategy

  • 多态更安全sealed + classDiscriminator 明确标记类型。

  • 忽略未知字段 :记得开 ignoreUnknownKeys = true,避免线上因后端加新字段而崩。

混淆 / 体积

  • 一般不需要额外 keep(因编译期生成并显式引用)。

  • 如果你自定义了 KSerializer 或用了反射式工厂,才可能需要补充 keep。

  • 体积优化:关闭 prettyPrint,必要时 explicitNulls=false,并按需关闭 encodeDefaults

常见坑与排查

  1. 忘记加插件id("org.jetbrains.kotlin.plugin.serialization")

  2. 字段名对不上 :用 @SerialName 或开 namingStrategy

  3. 接口偶发多字段ignoreUnknownKeys = true

  4. 后端大小写不一致JsonNamingStrategy.SnakeCase@JsonNames(...) 做兼容。

  5. 多态无法解析 :确认 classDiscriminator 字段名与后端一致;开放多态要注册 SerializersModule

  6. 时间类型 :优先 kotlinx-datetime;或写自定义 KSerializer

相关推荐
梦里不知身是客113 小时前
datax如何做增量导入
android
我是好小孩4 小时前
【Android】RecyclerView的高度问题、VH复用概念、多子项的实现;
android·java·网络
4Forsee4 小时前
【Android】模板化解决复杂场景的滑动冲突问题
android·java·rpc
彭同学学习日志4 小时前
解决 Android Navigation 组件导航栏配置崩溃:从错误到实现的完整指南
android·kotlin
法的空间4 小时前
让 Flutter 资源管理更智能
android·flutter·ios
江上清风山间明月5 小时前
Flutter中Column中使用ListView时溢出问题的解决方法
android·flutter·column·listview
01100001乄夵6 小时前
Android入门教程 - 第三章:Android布局全攻略
android·经验分享·笔记·学习方法·android期末学习
恋猫de小郭6 小时前
Snapchat 开源全新跨平台框架 Valdi ,一起来搞懂它究竟有什么特别之处
android·前端·flutter
我是好小孩14 小时前
【Android】布局优化:include、merge、ViewStub以及Inflate()源码浅析
android