07.Kotlin Serialization - Json工具配置

330 阅读8分钟

概述

Kotlin Serialization 的 JSON 工具提供了丰富的配置选项,允许开发者根据不同需求定制 JSON 序列化和反序列化行为。默认的 Json 实现严格遵循标准 JSON 规范,但通过创建自定义 Json 实例,可以支持许多非标准 JSON 特性。

注意:部分 API 处于实验阶段(ExperimentalSerializationApi),使用时需要添加相应注解。

创建自定义 JSON 实例

要使用自定义 JSON 格式配置,需要通过 Json() 构建器函数创建自定义 Json 实例:

val format = Json {
    prettyPrint = true
    ignoreUnknownKeys = true
    // 其他配置选项...
}

性能提示:建议存储和重用自定义Json实例,因为 Json 会缓存序列化器相关信息以提升性能。

JSON 配置选项

影响范围说明:配置选项的影响范围分为三类:

  • 序列化:仅影响对象转 JSON 的过程
  • 反序列化:仅影响 JSON 转对象的过程
  • 双向:同时影响序列化和反序列化过程

每个配置选项都会明确标注其影响范围。

1. 美化打印 (Pretty Printing)

配置属性prettyPrint
默认值false
影响范围:序列化
功能:控制 JSON 输出是否包含缩进和换行,提高可读性。

val format = Json { prettyPrint = true }

@Serializable
data class Animal(val name: String, val species: String)

fun main() {
    println(format.encodeToString(Animal("Fluffy", "Cat")))
}

输出结果:

{
    "name": "Fluffy",
    "species": "Cat"
}

2. 宽松解析 (Lenient Parsing)

配置属性isLenient
默认值false
影响范围:反序列化
功能:放宽 JSON 解析限制,允许解析非标准格式的 JSON 数据。

val format = Json { isLenient = true }

@Serializable
data class Animal(val name: String, val age: Int)

fun main() {
    val data = format.decodeFromString<Animal>("""
        {
            name   : Fluffy,
            age    : "3"
        }
    """)
    println(data)
}

宽松解析支持以下非标准格式:

  • 不带引号的属性名
  • 不带引号的字符串值
  • 不带引号的枚举值
  • 字符串形式的数字值

3. 忽略未知属性 (Ignoring Unknown Keys)

配置属性ignoreUnknownKeys
默认值false
影响范围:反序列化
功能:在反序列化时忽略 JSON 中存在但 Kotlin 类中不存在的属性。默认情况下,未知属性会导致反序列化失败。

val format = Json { ignoreUnknownKeys = true }

@Serializable
data class Animal(val name: String)

fun main() {
    val data = format.decodeFromString<Animal>("""
        {"name":"Fluffy","species":"Cat"}
    """)
    println(data) 
    // 输出:Animal(name=Fluffy)
}

按类忽略未知属性

使用 @JsonIgnoreUnknownKeys 注解可以仅对特定类启用此功能:

@Serializable
@JsonIgnoreUnknownKeys
data class Animal(val name: String)

fun main() {
    // 此类会自动忽略未知属性,不会抛出异常
    val animal = Json.decodeFromString<Animal>("""{"name":"Fluffy","species":"Cat","age":3}""")
    println(animal) // Animal(name=Fluffy)
}

按属性处理特殊情况

对于特定属性,可以使用 @Transient 注解跳过序列化和反序列化:

@Serializable
data class Animal(
    val name: String,
    val species: String,
    @Transient val tempData: String = "default" // 不参与序列化/反序列化
)

fun main() {
    val json = """{"name":"Fluffy","species":"Cat","tempData":"ignored"}"""
    val animal = Json.decodeFromString<Animal>(json)
    println(animal) // Animal(name=Fluffy, species=Cat, tempData=default)
    
    val encoded = Json.encodeToString(animal)
    println(encoded) // {"name":"Fluffy","species":"Cat"}
}

也可以使用 @SerialName 配合条件逻辑来处理属性级别的特殊情况:

@Serializable
data class FlexibleAnimal(
    val name: String,
    @SerialName("type") val species: String? = null,
    val details: Map<String, String> = emptyMap()
)

fun main() {
    // 可以处理不同结构的 JSON 数据
    val json1 = """{"name":"Fluffy","type":"Cat"}"""
    val json2 = """{"name":"Buddy","species":"Dog","details":{"color":"brown"}}"""
    
    val animal1 = Json.decodeFromString<FlexibleAnimal>(json1)
    val animal2 = Json { ignoreUnknownKeys = true }.decodeFromString<FlexibleAnimal>(json2)
    
    println(animal1) // FlexibleAnimal(name=Fluffy, species=Cat, details={})
    println(animal2) // FlexibleAnimal(name=Buddy, species=null, details={})
}

4. 备选 JSON 属性名 (Alternative Json Names)

注解@JsonNames
影响范围:反序列化
功能:为单个 Kotlin 属性支持多个 JSON 字段名称,提高 API 兼容性。

@OptIn(ExperimentalSerializationApi::class)
@Serializable
data class Animal(@JsonNames("petName") val name: String)

fun main() {
    // 两种 JSON 字段名都可以正确解析
    val animal1 = Json.decodeFromString<Animal>("""{"name":"Fluffy"}""")
    val animal2 = Json.decodeFromString<Animal>("""{"petName":"Buddy"}""")
    
    println(animal1) // Animal(name=Fluffy)
    println(animal2) // Animal(name=Buddy)
}

5. 编码默认值 (Encoding Defaults)

配置属性encodeDefaults
默认值false
影响范围:序列化
功能:控制是否在 JSON 中输出属性的默认值。

val format = Json { encodeDefaults = true }

@Serializable
class Animal(
    val name: String,
    val species: String = "Cat",
    val owner: String? = null
)

fun main() {
    val data = Animal("Fluffy")
    println(format.encodeToString(data))
    // 输出:{"name":"Fluffy","species":"Cat","owner":null}
}

按属性控制默认值编码

使用 @EncodeDefault 注解可以精确控制特定属性的默认值编码行为,无需全局设置:

@Serializable
data class Animal(
    val name: String,
    @EncodeDefault val species: String = "Cat",           // 总是编码
    @EncodeDefault(EncodeDefault.Mode.NEVER) val age: Int = 0,  // 从不编码
    val owner: String? = null                             // 跟随全局设置
)

fun main() {
    val animal = Animal("Fluffy")
    
    // 不设置 encodeDefaults,默认为 false
    println(Json.encodeToString(animal))
    // 输出:{"name":"Fluffy","species":"Cat"}
    // species 被编码(因为 @EncodeDefault),age 不被编码(因为 NEVER)
    
    // 设置 encodeDefaults = true
    val format = Json { encodeDefaults = true }
    println(format.encodeToString(animal))
    // 输出:{"name":"Fluffy","species":"Cat","owner":null}
    // age 仍然不被编码(因为 @EncodeDefault(NEVER) 优先级更高)
}

@EncodeDefault** 模式说明**:

  • @EncodeDefault(默认):总是编码此属性的默认值
  • @EncodeDefault(EncodeDefault.Mode.ALWAYS):同上,显式指定
  • @EncodeDefault(EncodeDefault.Mode.NEVER):从不编码此属性的默认值

6. 显式空值 (Explicit Nulls)

配置属性explicitNulls
默认值true
影响范围:双向
功能:控制可空属性的 null 值处理方式。

当设置为 false 时:

  • 序列化:null 值属性不会输出到 JSON 中
  • 反序列化:缺失的字段被视为 null 或默认值
val format = Json { explicitNulls = false }

@Serializable
data class Animal(
    val name: String,
    val species: String,
    val age: Int? = 3,
    val owner: String?,
    val description: String? = null
)

fun main() {
    val data = Animal("Fluffy", "Cat", null, null, null)
    val json = format.encodeToString(data)
    println(json)
    // 输出:{"name":"Fluffy","species":"Cat"}
    
    println(format.decodeFromString<Animal>(json))
    // 输出:Animal(name=Fluffy, species=Cat, age=3, owner=null, description=null)
}

示例说明

  • age 属性在encode前是 null,encode不会序列化age字段,decode后变成了 3(默认值)
  • owner 属性没有默认值,所以decode后仍然是 null
  • description 属性有默认值 null,decode后保持为 null

注意:当 explicitNulls = false 时,具有非空默认值的可空属性的编码/解码会变得不对称。这种行为在处理可选字段时很有用,但需要注意数据一致性。

7. 强制输入值 (Coercing Input Values)

配置属性coerceInputValues
默认值false
影响范围:反序列化
功能:将无效输入值转换为默认值或 null,提高容错性。

支持处理的无效值:

  • 非空类型接收到 null
  • 枚举类型接收到未知值
val format = Json { coerceInputValues = true }

@Serializable
data class Animal(val name: String, val species: String = "Cat")

fun main() {
    val data = format.decodeFromString<Animal>("""
        {"name":"Fluffy","species":null}
    """)
    println(data) // Animal(name=Fluffy, species=Cat)
}

8. 允许复杂类型作为 Map 键 (Allowing Structured Map Keys)

配置属性allowStructuredMapKeys
默认值false
影响范围:双向
功能:允许使用自定义类作为 Map 的键类型。

val format = Json { allowStructuredMapKeys = true }

@Serializable
data class Animal(val name: String)

fun main() {
    val map = mapOf(
        Animal("Fluffy") to "Cat",
        Animal("Buddy") to "Dog"
    )
    println(format.encodeToString(map))
    // 输出:[{"name":"Fluffy"},"Cat",{"name":"Buddy"},"Dog"]
    // 注意:Map 被序列化为交替的键值数组
}

9. 允许特殊浮点值 (Allowing Special Floating-Point Values)

配置属性allowSpecialFloatingPointValues
默认值false
影响范围:双向
功能:支持序列化 NaNInfinity 等特殊浮点值。

val format = Json { allowSpecialFloatingPointValues = true }

@Serializable
class Animal(val weight: Double)

fun main() {
    val data = Animal(Double.NaN)
    println(format.encodeToString(data))
    // 输出:{"weight":NaN}
    // 注意:这不是标准 JSON,但在某些场景下很有用
}

10. 类判别器输出模式 (Class Discriminator Output Mode)

配置属性classDiscriminatorMode
默认值ClassDiscriminatorMode.POLYMORPHIC
影响范围:序列化
功能:控制多态类型判别器的输出策略。

可选值

  • POLYMORPHIC(默认):仅为多态类添加判别器
  • NONE:不添加判别器
  • ALL_JSON_OBJECTS:为所有可能的对象添加判别器

11. 枚举反序列化不区分大小写 (Case-Insensitive Enum Decoding)

配置属性decodeEnumsCaseInsensitive
默认值false
影响范围:反序列化
功能:在反序列化时忽略枚举值的大小写。

@OptIn(ExperimentalSerializationApi::class)
val format = Json { decodeEnumsCaseInsensitive = true }

enum class AnimalType { DOMESTIC, @JsonNames("Wild") WILD }

@Serializable
data class AnimalList(val animals: List<AnimalType>)

fun main() {
    println(format.decodeFromString<AnimalList>("""{"animals":["domestic", "wild"]}"""))
    // 输出:AnimalList(animals=[DOMESTIC, WILD])
}

12. 全局命名策略 (Global Naming Strategy)

配置属性namingStrategy
默认值:无(使用原始属性名)
影响范围:双向
功能:为所有属性名应用统一的命名转换策略。

可选策略

  • JsonNamingStrategy.SnakeCase:下划线命名(如:animal_name
  • JsonNamingStrategy.KebabCase:短横线命名(如:animal-name
@Serializable
data class Animal(val animalName: String, val animalOwner: String)

@OptIn(ExperimentalSerializationApi::class)
val snakeCaseFormat = Json { namingStrategy = JsonNamingStrategy.SnakeCase }

@OptIn(ExperimentalSerializationApi::class)
val kebabCaseFormat = Json { namingStrategy = JsonNamingStrategy.KebabCase }

fun main() {
    val animal = Animal("Fluffy", "Alice")
    
    // SnakeCase 示例
    println("SnakeCase:")
    println(snakeCaseFormat.encodeToString(animal))
    // 输出:{"animal_name":"Fluffy","animal_owner":"Alice"}
    
    val decodedSnake = snakeCaseFormat.decodeFromString<Animal>("""{"animal_name":"Buddy", "animal_owner":"Bob"}""")
    println(decodedSnake)
    // 输出:Animal(animalName=Buddy, animalOwner=Bob)
    
    // KebabCase 示例
    println("\nKebabCase:")
    println(kebabCaseFormat.encodeToString(animal))
    // 输出:{"animal-name":"Fluffy","animal-owner":"Alice"}
    
    val decodedKebab = kebabCaseFormat.decodeFromString<Animal>("""{"animal-name":"Charlie", "animal-owner":"Carol"}""")
    println(decodedKebab)
    // 输出:Animal(animalName=Charlie, animalOwner=Carol)
}

使用注意

  • 命名策略应用于所有属性,包括使用 @SerialName 指定的属性
  • 可能在复杂类层次中导致名称冲突
  • 影响 IDE 的重构和查找功能
  • 建议在项目初期统一规划使用

基础配置总结

本文档涵盖了 Kotlin Serialization 的基础 JSON 配置选项,包括:

  1. 美化打印 - 控制 JSON 输出格式
  2. 宽松解析 - 支持非标准 JSON 格式
  3. 忽略未知属性 - 处理 API 演进和兼容性
  4. 备选 JSON 属性名 - 支持多个字段名称
  5. 编码默认值 - 控制默认值的输出
  6. 显式空值 - 管理 null 值的处理
  7. 强制输入值 - 处理无效输入数据
  8. 允许非基础类型作为Map的key - 支持复杂键类型
  9. 允许特殊浮点值 - 处理 NaN 和无穷大
  10. 类判别器输出模式 - 控制多态类型信息
  11. 枚举解码不区分大小写 - 灵活的枚举处理
  12. 全局命名策略 - 统一的属性名称转换

这些基础配置选项为大多数常见的 JSON 序列化需求提供了解决方案。每个选项都明确标注了影响范围,帮助开发者理解其适用范围。