Kotlin数据类中Duration成员反序列化异常问题排查
首先,你的代码存在一个明显的语法错误:次级构造函数调用主构造函数时多传了一个参数。主构造函数仅包含3个参数(Long、String、Duration),但你写的this(0, "", Duration.ZERO, "")多了一个空字符串,会导致编译失败,先修正为:
@Serializable data class DataClass( val fieldOne: Long, val fieldTwo: String, val fieldThree: Duration, ) { constructor() : this(0, "", Duration.ZERO) }
接下来是核心的反序列化问题:
你遇到的fieldThree反序列化后变为0的问题,本质是Firebase Realtime Database的默认反序列化机制与kotlinx.serialization的配合冲突,或者你没有让Firebase正确识别Duration类型的序列化逻辑。
关键原因
Firebase Realtime Database默认使用基于Java反射的序列化/反序列化(遵循JavaBean规范),而非直接调用kotlinx.serialization的逻辑。即便你添加了@Serializable注解,Firebase默认也不会采用它处理对象。反序列化时,Firebase会先调用无参构造函数创建实例(此时fieldThree被设为Duration.ZERO),再尝试通过反射为字段赋值——但Firebase默认不识别Duration类型,无法将数据库中存储的毫秒数值转换为Duration对象,最终fieldThree会保持构造函数里的ZERO值。
解决方案
方案1:让Firebase使用kotlinx.serialization处理序列化/反序列化
为Firebase配置自定义转换器,将所有序列化/反序列化逻辑交给kotlinx.serialization的Json处理:
import com.google.firebase.database.DataSnapshot import com.google.firebase.database.DatabaseError import com.google.firebase.database.ValueEventListener import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json // 序列化:将对象转为JSON字符串存入Firebase fun <T> serializeToFirebase(value: T, serializer: kotlinx.serialization.KSerializer<T>): String { return Json.encodeToString(serializer, value) } // 反序列化:从Firebase读取JSON字符串转为对象 fun <T> deserializeFromFirebase(json: String, serializer: kotlinx.serialization.KSerializer<T>): T { return Json.decodeFromString(serializer, json) } // 存储数据示例 val data = DataClass(12345, "", Duration.hours(1)) val json = Json.encodeToString(DataClass.serializer(), data) firebaseDatabaseReference.setValue(json) // 读取数据示例 firebaseDatabaseReference.addListenerForSingleValueEvent(object : ValueEventListener { override fun onDataChange(snapshot: DataSnapshot) { val json = snapshot.getValue(String::class.java) ?: "" val data = Json.decodeFromString(DataClass.serializer(), json) // 此时data.fieldThree会是正确的1小时时长 } override fun onCancelled(error: DatabaseError) { // 处理错误逻辑 } })
这种方式下,Firebase仅存储JSON字符串,所有类型转换逻辑由kotlinx.serialization处理,能完美支持Duration类型。
方案2:为Firebase自定义Duration类型的TypeAdapter
若希望继续使用Firebase的默认对象存储方式(不转JSON字符串),可以为Duration编写Firebase兼容的TypeAdapter,让Firebase能正确转换时长与毫秒数值:
import com.google.gson.TypeAdapter import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonWriter import java.time.Duration class DurationTypeAdapter : TypeAdapter<Duration>() { override fun write(out: JsonWriter, value: Duration?) { if (value == null) { out.nullValue() } else { out.value(value.toMillis()) } } override fun read(`in`: JsonReader): Duration { val millis = `in`.nextLong() return Duration.ofMillis(millis) } }
然后在数据类的fieldThree字段上添加@JsonAdapter注解(需引入Gson依赖,Firebase默认用Gson处理序列化):
import com.google.gson.annotations.JsonAdapter import java.time.Duration import kotlinx.serialization.Serializable @Serializable data class DataClass( val fieldOne: Long, val fieldTwo: String, @JsonAdapter(DurationTypeAdapter::class) val fieldThree: Duration, ) { constructor() : this(0, "", Duration.ZERO) }
这样Firebase反序列化时,会调用自定义的TypeAdapter将数据库中的毫秒数转换为Duration对象,不再保持ZERO值。
额外注意点
- 确保kotlinx.serialization配置正确:在
build.gradle中添加kotlinx-serialization-core依赖,并启用序列化插件。 - 避免同时混用kotlinx.serialization与Firebase默认序列化机制,选一种方式保持一致,防止冲突。
内容的提问来源于stack exchange,提问作者tomerpacific

