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
相关产品推荐
相关产品推荐

