Google Cloud Functions RTDB触发器如何将delta JSON反序列化为POJO?
解决方案
报错的根本原因确认为Firebase Realtime Database的默认存储逻辑:当数组存在空值、或索引不连续时,为避免稀疏数组的空间浪费,Firebase会自动将数组转换为以整数为键的JSON对象,因此Gson默认反序列化时无法匹配POJO中声明的List<T>类型字段,抛出类型不匹配异常。
方案1:自定义Gson类型适配器(无侵入,无需修改原有POJO)
这个方案通过扩展Gson的反序列化逻辑,自动识别整数键的Map并转换为List,全局注册后原有反序列化代码完全不用改动。
首先给出Kotlin版本的适配器实现:
import com.google.gson.* import com.google.gson.reflect.TypeToken import java.lang.reflect.Type class ListTypeAdapter<T> : TypeAdapter<List<T>>() { private val gson = Gson() override fun write(out: JsonWriter, value: List<T>?) { // 序列化逻辑按需实现,如果不需要序列化可以正常实现数组输出逻辑 gson.toJson(value, object : TypeToken<List<T>>() {}.type, out) } override fun read(`in`: JsonReader): List<T> { val jsonElement = JsonParser.parseReader(`in`) // 如果是标准数组格式,正常解析 if (jsonElement.isJsonArray) { return gson.fromJson(jsonElement, object : TypeToken<List<T>>() {}.type) } // 如果是Firebase转换后的整数键对象,转换为有序List if (jsonElement.isJsonObject) { val jsonObject = jsonElement.asJsonObject val list = mutableListOf<T?>() jsonObject.entrySet().forEach { (key, value) -> // 尝试将键转为整数作为数组索引,非整数键直接忽略 val index = key.toIntOrNull() ?: return@forEach // 扩展List长度到索引+1,空缺位置补null while (list.size <= index) { list.add(null) } list[index] = gson.fromJson(value, object : TypeToken<T>() {}.type) } // 不需要保留索引对应关系的话可以在这里调用.filterNotNull()过滤空值 @Suppress("UNCHECKED_CAST") return list as List<T> } return emptyList() } }
将适配器注册到Gson实例:
val gson = GsonBuilder() .registerTypeAdapter(object : TypeToken<List<*>>() {}.type, ListTypeAdapter<Any>()) // 也可以针对特定元素类型的List单独注册,适配性更高 .create()
注册完成后原有反序列化代码不需要做任何修改即可正常运行:
val mObject = gson.fromJson(body.get("delta").toString(), MyCustomObject::class.java)
方案2:使用Firebase内置序列化工具(兼容性最优)
如果项目中已经引入了Firebase Admin SDK,可以直接使用Firebase Realtime Database自带的序列化逻辑,它本身原生支持整数键对象到List的自动转换,和客户端SDK的转换规则完全一致:
import com.google.gson.reflect.TypeToken import com.google.firebase.database.core.utilities.encoding.CustomClassMapper // 先把delta转为通用Map结构 val deltaMap = gson.fromJson(body.get("delta"), object : TypeToken<Map<String, Any>>() {}.type) // 直接调用Firebase工具转换为目标POJO val mObject = CustomClassMapper.convertToCustomClass(deltaMap, MyCustomObject::class.java)
这个方案不需要额外编写适配代码,兼容Firebase所有默认的类型转换规则。
注意事项
- 如果原RTDB中的数组存在不连续索引,转换后的List会在空缺索引位置填充null,业务代码需要做好空值校验
- 如果不需要保留索引对应关系,可以在适配器中直接按整数键排序后提取值,忽略空缺的索引,避免生成大量空值的List
内容的提问来源于stack exchange,提问作者Geraldo Neto
相关产品推荐
相关产品推荐

