Kotlinx Serialization 指南

欢迎访问我的主页: https://heeheeaii.github.io/

1. 基础配置

项目配置

kotlin 复制代码
// build.gradle.kts (Module)
plugins {
    kotlin("jvm") version "1.9.10"
    kotlin("plugin.serialization") version "1.9.10"
}

dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0")
}

基本使用

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

@Serializable
data class User(
    val id: Int,
    val name: String,
    val email: String
)

fun main() {
    val user = User(1, "张三", "zhangsan@example.com")
    
    // 序列化:对象 → JSON 字符串
    val jsonString = Json.encodeToString(user)
    println(jsonString)
    // {"id":1,"name":"张三","email":"zhangsan@example.com"}
    
    // 反序列化:JSON 字符串 → 对象
    val decodedUser = Json.decodeFromString<User>(jsonString)
    println(decodedUser)
    // User(id=1, name=张三, email=zhangsan@example.com)
}

2. 字段映射和配置

@SerialName - 字段名映射

kotlin 复制代码
@Serializable
data class User(
    val id: Int,
    @SerialName("user_name") 
    val name: String,
    @SerialName("email_address")
    val email: String,
    @SerialName("created_at")
    val createdAt: String
)

fun testSerialName() {
    val user = User(1, "张三", "zhang@example.com", "2023-01-01")
    val json = Json.encodeToString(user)
    println(json)
    // {"id":1,"user_name":"张三","email_address":"zhang@example.com","created_at":"2023-01-01"}
}

可选字段和默认值

kotlin 复制代码
@Serializable
data class Product(
    val id: Int,
    val name: String,
    val price: Double,
    val description: String? = null,  // 可为空
    val category: String = "未分类",   // 默认值
    val isActive: Boolean = true
)

fun testDefaultValues() {
    // JSON 中缺少某些字段
    val json = """{"id":1,"name":"iPhone","price":999.99}"""
    
    val product = Json.decodeFromString<Product>(json)
    println(product)
    // Product(id=1, name=iPhone, price=999.99, description=null, category=未分类, isActive=true)
}

@Transient - 忽略字段

kotlin 复制代码
@Serializable
data class User(
    val id: Int,
    val name: String,
    @Transient 
    val password: String = "",  // 不会被序列化
    @Transient
    val tempData: Map<String, Any> = emptyMap()
)

fun testTransient() {
    val user = User(1, "张三", "secret123", mapOf("cache" to "data"))
    val json = Json.encodeToString(user)
    println(json)
    // {"id":1,"name":"张三"}  // password 和 tempData 被忽略
}

3. 集合和复杂类型

基本集合

kotlin 复制代码
@Serializable
data class UserList(
    val users: List<User>,
    val tags: Set<String>,
    val metadata: Map<String, String>
)

fun testCollections() {
    val userList = UserList(
        users = listOf(
            User(1, "张三", "zhang@example.com"),
            User(2, "李四", "li@example.com")
        ),
        tags = setOf("admin", "user"),
        metadata = mapOf("total" to "2", "page" to "1")
    )
    
    val json = Json.encodeToString(userList)
    println(Json { prettyPrint = true }.encodeToString(userList))
}

// 直接序列化集合
fun testDirectCollections() {
    val users = listOf(
        User(1, "张三", "zhang@example.com"),
        User(2, "李四", "li@example.com")
    )
    
    // List<User> 直接序列化
    val json = Json.encodeToString(users)
    println(json)
    
    // 反序列化为 List<User>
    val decodedUsers = Json.decodeFromString<List<User>>(json)
    println(decodedUsers)
}

嵌套对象

kotlin 复制代码
@Serializable
data class Address(
    val street: String,
    val city: String,
    val zipCode: String
)

@Serializable
data class Company(
    val id: Int,
    val name: String,
    val address: Address,  // 嵌套对象
    val employees: List<User>  // 嵌套列表
)

fun testNestedObjects() {
    val company = Company(
        id = 1,
        name = "科技有限公司",
        address = Address("中关村大街1号", "北京", "100000"),
        employees = listOf(
            User(1, "张三", "zhang@example.com"),
            User(2, "李四", "li@example.com")
        )
    )
    
    val json = Json { prettyPrint = true }.encodeToString(company)
    println(json)
}

4. 多态序列化

密封类 (Sealed Class)

kotlin 复制代码
@Serializable
sealed class ApiResponse {
    @Serializable
    @SerialName("success")
    data class Success(val data: String, val timestamp: Long) : ApiResponse()
    
    @Serializable
    @SerialName("error")
    data class Error(val message: String, val code: Int) : ApiResponse()
    
    @Serializable
    @SerialName("loading")
    object Loading : ApiResponse()
}

fun testPolymorphism() {
    val responses: List<ApiResponse> = listOf(
        ApiResponse.Success("数据获取成功", System.currentTimeMillis()),
        ApiResponse.Error("网络错误", 500),
        ApiResponse.Loading
    )
    
    // 序列化多态对象
    val json = Json { prettyPrint = true }.encodeToString(responses)
    println(json)
    
    // 反序列化
    val decodedResponses = Json.decodeFromString<List<ApiResponse>>(json)
    decodedResponses.forEach { response ->
        when (response) {
            is ApiResponse.Success -> println("成功: ${response.data}")
            is ApiResponse.Error -> println("错误: ${response.message}")
            is ApiResponse.Loading -> println("加载中...")
        }
    }
}

抽象类多态

kotlin 复制代码
@Serializable
@JsonClassDiscriminator("type")
abstract class Shape

@Serializable
@SerialName("circle")
data class Circle(val radius: Double) : Shape()

@Serializable
@SerialName("rectangle")
data class Rectangle(val width: Double, val height: Double) : Shape()

@Serializable
@SerialName("triangle")
data class Triangle(val base: Double, val height: Double) : Shape()

fun testAbstractPolymorphism() {
    val shapes: List<Shape> = listOf(
        Circle(5.0),
        Rectangle(10.0, 8.0),
        Triangle(6.0, 4.0)
    )
    
    val json = Json { prettyPrint = true }.encodeToString(shapes)
    println(json)
    
    val decodedShapes = Json.decodeFromString<List<Shape>>(json)
    println(decodedShapes)
}

5. 自定义序列化器

日期时间序列化

kotlin 复制代码
@Serializable
data class Event(
    val id: Int,
    val name: String,
    @Serializable(with = DateTimeSerializer::class)
    val createdAt: LocalDateTime
)

object DateTimeSerializer : KSerializer<LocalDateTime> {
    private val formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME
    
    override val descriptor: SerialDescriptor = 
        PrimitiveSerialDescriptor("LocalDateTime", PrimitiveKind.STRING)
    
    override fun serialize(encoder: Encoder, value: LocalDateTime) {
        encoder.encodeString(value.format(formatter))
    }
    
    override fun deserialize(decoder: Decoder): LocalDateTime {
        return LocalDateTime.parse(decoder.decodeString(), formatter)
    }
}

fun testCustomSerializer() {
    val event = Event(1, "会议", LocalDateTime.now())
    val json = Json.encodeToString(event)
    println(json)
    
    val decodedEvent = Json.decodeFromString<Event>(json)
    println(decodedEvent)
}

枚举序列化

kotlin 复制代码
@Serializable
enum class Status(val value: String) {
    @SerialName("active") ACTIVE("激活"),
    @SerialName("inactive") INACTIVE("未激活"),
    @SerialName("pending") PENDING("待审核");
    
    @Serializer(forClass = Status::class)
    companion object : KSerializer<Status> {
        override val descriptor: SerialDescriptor = 
            PrimitiveSerialDescriptor("Status", PrimitiveKind.STRING)
        
        override fun serialize(encoder: Encoder, value: Status) {
            encoder.encodeString(value.name.lowercase())
        }
        
        override fun deserialize(decoder: Decoder): Status {
            val name = decoder.decodeString().uppercase()
            return Status.valueOf(name)
        }
    }
}

@Serializable
data class User(
    val id: Int,
    val name: String,
    val status: Status
)

BigDecimal 序列化器

kotlin 复制代码
object BigDecimalSerializer : KSerializer<BigDecimal> {
    override val descriptor: SerialDescriptor = 
        PrimitiveSerialDescriptor("BigDecimal", PrimitiveKind.STRING)
    
    override fun serialize(encoder: Encoder, value: BigDecimal) {
        encoder.encodeString(value.toPlainString())
    }
    
    override fun deserialize(decoder: Decoder): BigDecimal {
        return BigDecimal(decoder.decodeString())
    }
}

@Serializable
data class Product(
    val id: Int,
    val name: String,
    @Serializable(with = BigDecimalSerializer::class)
    val price: BigDecimal
)

6. Json 配置选项

常用配置

kotlin 复制代码
val json = Json {
    // 忽略 JSON 中未知的字段
    ignoreUnknownKeys = true
    
    // 美化输出
    prettyPrint = true
    
    // 类型强制转换 (如字符串 "1" 转为数字 1)
    coerceInputValues = true
    
    // 编码默认值
    encodeDefaults = true
    
    // 允许结构化键 (Map 的键可以是复杂对象)
    allowStructuredMapKeys = true
    
    // 允许特殊浮点值 (NaN, Infinity)
    allowSpecialFloatingPointValues = true
}

@Serializable
data class Config(
    val name: String,
    val age: Int = 18,
    val isActive: Boolean = true
)

fun testJsonConfig() {
    val config = Config("测试")
    
    // 使用配置的 Json 实例
    val jsonString = json.encodeToString(config)
    println(jsonString)
    
    // 解析包含未知字段的 JSON
    val jsonWithExtra = """
        {
            "name": "测试",
            "age": "25",
            "isActive": true,
            "unknownField": "这个字段会被忽略"
        }
    """.trimIndent()
    
    val decoded = json.decodeFromString<Config>(jsonWithExtra)
    println(decoded)
    // Config(name=测试, age=25, isActive=true)
}

自定义命名策略

kotlin 复制代码
// 使用自定义命名策略
val snakeCaseJson = Json {
    namingStrategy = JsonNamingStrategy.SnakeCase
}

@Serializable
data class UserProfile(
    val userId: Int,
    val firstName: String,
    val lastName: String,
    val emailAddress: String
)

fun testNamingStrategy() {
    val profile = UserProfile(1, "张", "三", "zhangsan@example.com")
    
    val json = snakeCaseJson.encodeToString(profile)
    println(json)
    // {"user_id":1,"first_name":"张","last_name":"三","email_address":"zhangsan@example.com"}
}

7. 错误处理和验证

异常处理

kotlin 复制代码
fun safeDeserialization() {
    val invalidJson = """{"id": "not_a_number", "name": "张三"}"""
    
    try {
        val user = Json.decodeFromString<User>(invalidJson)
        println(user)
    } catch (e: SerializationException) {
        println("序列化错误: ${e.message}")
        // 可以提供默认值或其他处理逻辑
    }
}

// 更优雅的错误处理
fun safeParseUser(jsonString: String): Result<User> {
    return try {
        Result.success(Json.decodeFromString<User>(jsonString))
    } catch (e: SerializationException) {
        Result.failure(e)
    }
}

条件序列化

kotlin 复制代码
@Serializable
data class ConditionalUser(
    val id: Int,
    val name: String,
    @EncodeDefault(EncodeDefault.Mode.NEVER)  // 从不编码默认值
    val role: String = "user",
    @EncodeDefault(EncodeDefault.Mode.ALWAYS) // 总是编码默认值
    val isActive: Boolean = true
)

8. 与网络库集成

与 OkHttp + Retrofit 集成

kotlin 复制代码
// Retrofit 配置
val retrofit = Retrofit.Builder()
    .baseUrl("https://api.example.com/")
    .addConverterFactory(
        Json.asConverterFactory("application/json".toMediaType())
    )
    .client(okHttpClient)
    .build()

// API 接口
interface ApiService {
    @GET("users/{id}")
    suspend fun getUser(@Path("id") id: Int): User
    
    @POST("users")
    suspend fun createUser(@Body user: User): User
    
    @GET("users")
    suspend fun getUsers(): List<User>
}

与 Ktor Client 集成

kotlin 复制代码
val client = HttpClient {
    install(ContentNegotiation) {
        json(Json {
            prettyPrint = true
            ignoreUnknownKeys = true
        })
    }
}

// 使用
suspend fun fetchUser(id: Int): User {
    return client.get("https://api.example.com/users/$id").body()
}

suspend fun createUser(user: User): User {
    return client.post("https://api.example.com/users") {
        contentType(ContentType.Application.Json)
        setBody(user)
    }.body()
}

9. 实际应用示例

API 响应包装

kotlin 复制代码
@Serializable
data class ApiResponse<T>(
    val code: Int,
    val message: String,
    val data: T? = null,
    val timestamp: Long = System.currentTimeMillis()
)

@Serializable
data class PaginatedResponse<T>(
    val items: List<T>,
    val total: Int,
    val page: Int,
    val pageSize: Int,
    val hasNext: Boolean
)

// 使用示例
suspend fun fetchUsers(page: Int = 1): ApiResponse<PaginatedResponse<User>> {
    val json = """
        {
            "code": 200,
            "message": "success",
            "data": {
                "items": [
                    {"id": 1, "name": "张三", "email": "zhang@example.com"},
                    {"id": 2, "name": "李四", "email": "li@example.com"}
                ],
                "total": 100,
                "page": 1,
                "pageSize": 10,
                "hasNext": true
            },
            "timestamp": 1635724800000
        }
    """.trimIndent()
    
    return Json.decodeFromString<ApiResponse<PaginatedResponse<User>>>(json)
}

配置文件处理

kotlin 复制代码
@Serializable
data class AppConfig(
    val server: ServerConfig,
    val database: DatabaseConfig,
    val logging: LoggingConfig
)

@Serializable
data class ServerConfig(
    val host: String = "localhost",
    val port: Int = 8080,
    val ssl: Boolean = false
)

@Serializable
data class DatabaseConfig(
    val url: String,
    val username: String,
    val password: String,
    val maxConnections: Int = 10
)

@Serializable
data class LoggingConfig(
    val level: String = "INFO",
    val file: String? = null
)

// 从文件读取配置
fun loadConfig(configFile: File): AppConfig {
    val json = configFile.readText()
    return Json.decodeFromString<AppConfig>(json)
}

// 保存配置到文件
fun saveConfig(config: AppConfig, configFile: File) {
    val json = Json { prettyPrint = true }.encodeToString(config)
    configFile.writeText(json)
}

10. 最佳实践总结

1. 注解使用

kotlin 复制代码
@Serializable
data class BestPracticeUser(
    val id: Int,
    
    // 使用 SerialName 映射 API 字段名
    @SerialName("user_name")
    val name: String,
    
    // 可选字段设置默认值
    val email: String? = null,
    
    // 敏感信息不序列化
    @Transient
    val password: String = "",
    
    // 自定义序列化器处理复杂类型
    @Serializable(with = DateTimeSerializer::class)
    val createdAt: LocalDateTime,
    
    // 默认值处理
    val role: String = "user",
    val isActive: Boolean = true
)

2. 全局 Json 配置

kotlin 复制代码
object JsonConfig {
    val default = Json {
        ignoreUnknownKeys = true
        coerceInputValues = true
        encodeDefaults = false
        prettyPrint = BuildConfig.DEBUG  // 仅在 Debug 模式下格式化
    }
}

// 在整个应用中使用统一配置
val user = JsonConfig.default.decodeFromString<User>(jsonString)

3. 错误处理

kotlin 复制代码
inline fun <reified T> String.parseJson(): Result<T> {
    return try {
        Result.success(Json.decodeFromString<T>(this))
    } catch (e: SerializationException) {
        Result.failure(e)
    }
}

// 使用
val result = jsonString.parseJson<User>()
result.fold(
    onSuccess = { user -> println("解析成功: $user") },
    onFailure = { error -> println("解析失败: ${error.message}") }
)
相关推荐
FunnySaltyFish7 小时前
什么?Compose 把 GapBuffer 换成了 LinkBuffer?
算法·kotlin·android jetpack
Kapaseker13 小时前
Compose 进阶—巧用 GraphicsLayer
android·kotlin
Kapaseker1 天前
实战 Compose 中的 IntrinsicSize
android·kotlin
A0微声z3 天前
Kotlin Multiplatform (KMP) 中使用 Protobuf
kotlin
alexhilton4 天前
使用FunctionGemma进行设备端函数调用
android·kotlin·android jetpack
lhDream4 天前
Kotlin 开发者必看!JetBrains 开源 LLM 框架 Koog 快速上手指南(含示例)
kotlin
RdoZam4 天前
Android-封装基类Activity\Fragment,从0到1记录
android·kotlin
Kapaseker5 天前
研究表明,开发者对Kotlin集合的了解不到 20%
android·kotlin
糖猫猫cc5 天前
Kite:两种方式实现动态表名
java·kotlin·orm·kite
如此风景5 天前
kotlin协程学习小计
android·kotlin