You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin自定义序列化:嵌套数据类的扁平化JSON结构实现

问题描述

现有如下Kotlin数据类:

data class Base (
  val prop1: String,
  val prop2: String
)

data class Derived (
  val base: Base,
  val prop3: String
)

期望Derived类型的序列化结果为平结构:

{
  "prop1": "value1",
  "prop2": "value2",
  "prop3": "value3"
}

目前可为Derived编写kotlinx.serialization自定义序列化器,但存在大量类似Derived、包含Base字段的类,不想逐个编写序列化/反序列化器,希望找到更便捷的实现方式,同时也想了解其他库(比如Jackson)是否能解决该问题。


一、kotlinx.serialization 实现方案

1. 委托属性+@Transient 快速展开

为包含Base的类添加委托属性,直接暴露Base的字段,同时标记原base字段为@Transient避免被序列化:

import kotlinx.serialization.Transient
import kotlinx.serialization.Serializable

@Serializable
data class Base(
    val prop1: String,
    val prop2: String
)

@Serializable
data class Derived(
    @Transient val base: Base,
    val prop3: String
) {
    val prop1: String by base::prop1
    val prop2: String by base::prop2
}

新增类时仅需添加对应委托属性,无需编写序列化器,代码简洁统一。

2. 通用注解+上下文序列化器

编写一次通用序列化器,搭配自定义注解标记需要展开的Base字段,后续新增类仅需添加注解:

import kotlinx.serialization.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*

// 自定义注解标记需要展开的Base字段
@Target(AnnotationTarget.PROPERTY)
annotation class UnwrapBase

// 通用序列化器,处理所有标记了@UnwrapBase的Base字段
class UnwrapBaseSerializer<T>(private val originalSerializer: KSerializer<T>) : KSerializer<T> by originalSerializer {
    override fun serialize(encoder: Encoder, value: T) {
        val composite = encoder.beginStructure(descriptor)
        descriptor.elements.forEachIndexed { index, element ->
            val property = originalSerializer.properties[index]
            val propertyValue = property.get(value)
            if (propertyValue is Base && property.annotations.any { it is UnwrapBase }) {
                // 展开Base的字段
                composite.encodeStringElement(descriptor, descriptor.getElementIndex("prop1"), propertyValue.prop1)
                composite.encodeStringElement(descriptor, descriptor.getElementIndex("prop2"), propertyValue.prop2)
            } else {
                composite.encodeSerializableElement(descriptor, index, descriptor.getElementDescriptor(index), propertyValue)
            }
        }
        composite.endStructure(descriptor)
    }

    override fun deserialize(decoder: Decoder): T {
        val composite = decoder.beginStructure(descriptor)
        var baseProp1: String? = null
        var baseProp2: String? = null
        var otherProps = mutableMapOf<String, Any?>()
        
        loop@ while (true) {
            when (val index = composite.decodeElementIndex(descriptor)) {
                CompositeDecoder.DECODE_DONE -> break@loop
                descriptor.getElementIndex("prop1") -> baseProp1 = composite.decodeStringElement(descriptor, index)
                descriptor.getElementIndex("prop2") -> baseProp2 = composite.decodeStringElement(descriptor, index)
                else -> {
                    val elemDesc = descriptor.getElementDescriptor(index)
                    otherProps[descriptor.getElementName(index)] = composite.decodeSerializableElement(descriptor, index, elemDesc)
                }
            }
        }
        composite.endStructure(descriptor)
        
        // 通过反射或Kotlin构造函数生成实例,这里以Derived为例,通用场景可优化为反射实现
        return Derived(Base(baseProp1!!, baseProp2!!), otherProps["prop3"] as String) as T
    }
}

// 使用方式:给类指定序列化器,给base字段加注解
@Serializable(with = UnwrapBaseSerializer::class)
data class Derived(
    @UnwrapBase val base: Base,
    val prop3: String
)

只需编写一次通用序列化器,后续新增类仅需添加@Serializable(with = UnwrapBaseSerializer::class)和@UnwrapBase注解即可。


二、Jackson 实现方案

Jackson对嵌套对象的平序列化支持更便捷,无需大量自定义代码:

1. @JsonUnwrapped 注解直接展开

给base字段添加@JsonUnwrapped注解,自动实现平结构序列化与反序列化:

import com.fasterxml.jackson.annotation.JsonUnwrapped
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.registerKotlinModule

data class Base(
    val prop1: String,
    val prop2: String
)

data class Derived(
    @JsonUnwrapped val base: Base,
    val prop3: String
)

// 测试示例
fun main() {
    val mapper = ObjectMapper().registerKotlinModule()
    val derived = Derived(Base("value1", "value2"), "value3")
    println(mapper.writeValueAsString(derived))
    // 输出:{"prop1":"value1","prop2":"value2","prop3":"value3"}
}

新增类时仅需给嵌套的Base字段添加@JsonUnwrapped注解,无需编写任何序列化器,是最便捷的方案。

2. 全局配置自动展开所有Base字段

如果所有包含Base类型的字段都需要平序列化,可自定义全局序列化修改器,无需逐个添加注解:

import com.fasterxml.jackson.databind.*
import com.fasterxml.jackson.databind.ser.*
import com.fasterxml.jackson.module.kotlin.registerKotlinModule
import java.lang.reflect.Field

class BaseUnwrapModifier : BeanSerializerModifier() {
    override fun modifySerializer(config: SerializationConfig, desc: BeanDescription, serializer: JsonSerializer<*>): JsonSerializer<*> {
        if (serializer is BeanSerializer) {
            val properties = serializer.properties.toMutableList()
            val fields = desc.beanClass.declaredFields
            fields.forEach { field ->
                if (field.type == Base::class.java) {
                    field.isAccessible = true
                    // 添加Base的两个字段到序列化列表
                    properties.add(object : BeanPropertyWriter(serializer, desc.findProperty("prop1"), null, null, null, null, false, null) {
                        override fun getValue(value: Any): Any? = (field.get(value) as Base).prop1
                    })
                    properties.add(object : BeanPropertyWriter(serializer, desc.findProperty("prop2"), null, null, null, null, false, null) {
                        override fun getValue(value: Any): Any? = (field.get(value) as Base).prop2
                    })
                    // 移除原base字段的序列化项
                    properties.removeIf { it.name == field.name }
                }
            }
            return BeanSerializer(serializer, properties)
        }
        return super.modifySerializer(config, desc, serializer)
    }
}

// 使用方式:注册全局修改器
fun main() {
    val mapper = ObjectMapper().registerKotlinModule()
    mapper.registerModule(object : SimpleModule() {
        override fun setupModule(context: SetupContext) {
            super.setupModule(context)
            context.addBeanSerializerModifier(BaseUnwrapModifier())
        }
    })
    val derived = Derived(Base("value1", "value2"), "value3")
    println(mapper.writeValueAsString(derived))
}

配置完成后,所有包含Base字段的类都会自动平序列化,新增类无需任何额外操作。


内容的提问来源于stack exchange,提问作者Pazhamalai Arun

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 18:31:19