Firebase结合泛型与ProduceDto接口时出现序列化异常
Firebase Realtime Database 泛型批次类序列化问题解决指南
问题背景
我开发的农产品售卖应用中,需要按批次展示不同类型的农产品(豆类、土豆等),因此设计了泛型批次类LotDto<T:ProduceDto>,所有农产品类型(如FrijolDto、PotatoeDto)都实现ProduceDto接口,实现一套UI适配所有农产品的逻辑。
核心代码示例
泛型批次类LotDto
data class LotDto<out T:ProduceDto>( val lotId: String = "0", val quantity: Int = 0, val unitPriceOrigin: Int = 0, val unitPriceDestination: Int = 0, val destination: String = "ESTELI", val seller: UserDataDto = UserDataDto(), val dateCreated: String = "never", val dateUpdated: String = "never", val produce:T = FrijolDto() as T )
农产品实现类示例FrijolDto
data class FrijolDto( val category: String = "Frijol", val unitOfMeasurement: String = "qq", val variety: String = "Frijol Rojo", val humidity: Int = 100, val purity: Int = 0, val origin:String="Nowhere", val harvest: String = "primera", val photoPath: String = "", val isVerified: Boolean = false, val comment: String = "" //agregar curado ):ProduceDto{ override fun someMethodCommonToAllProduce(){} }
遇到的错误
使用Firebase Realtime Database反序列化时,执行如下代码:
dataSnapshot.getValue((GenericTypeIndicator<LotDto<@JvmSuppressWildcards FrijolDto>>(){}))
抛出异常:
com.google.firebase.database.DatabaseException: No properties to serialize found on class com.ccy.apps.frijolesonline.data.remote.dto.ProduceDto
现有方案的弊端
- 为每种农产品单独编写非泛型
Lot类会导致代码膨胀,破坏UI逻辑复用的设计。 - 临时将
produce字段存为HashMap会降低代码可读性,且容易引发数据不一致问题。 - 硬编码
LotDto和TransactionDto为仅支持FrijolDto时能正常读取数据,但失去泛型的灵活性。
可行解决方案
方案1:自定义序列化转换器处理接口类型
Firebase默认无法直接序列化/反序列化接口类型,需通过自定义转换器添加类型标识,实现接口到具体类的映射。
1. 编写ProduceDtoConverter
class ProduceDtoConverter : JsonSerializer<ProduceDto>, JsonDeserializer<ProduceDto> { override fun serialize(src: ProduceDto, typeOfSrc: Type, context: JsonSerializationContext): JsonElement { val jsonObj = JsonObject() // 添加类型标识,用于反序列化时识别具体实现类 jsonObj.addProperty("type", src.javaClass.simpleName) // 序列化具体类的所有字段 val srcJson = context.serialize(src) srcJson.asJsonObject.entrySet().forEach { jsonObj.add(it.key, it.value) } return jsonObj } override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): ProduceDto { val jsonObj = json.asJsonObject val type = jsonObj.get("type").asString // 根据类型标识匹配具体实现类 return when(type) { "FrijolDto" -> context.deserialize(json, FrijolDto::class.java) "PotatoeDto" -> context.deserialize(json, PotatoeDto::class.java) // 新增农产品类型时在此处添加分支 else -> throw IllegalArgumentException("未知农产品类型: $type") } } }
2. 为ProduceDto接口添加注解
@JsonAdapter(ProduceDtoConverter::class) interface ProduceDto { fun someMethodCommonToAllProduce() }
方案2:调整泛型协变修饰符
原LotDto使用out T:ProduceDto协变泛型,可能导致Firebase反序列化时无法正确解析类型。尝试将泛型改为普通约束:
data class LotDto<T:ProduceDto>( val lotId: String = "0", val quantity: Int = 0, val unitPriceOrigin: Int = 0, val unitPriceDestination: Int = 0, val destination: String = "ESTELI", val seller: UserDataDto = UserDataDto(), val dateCreated: String = "never", val dateUpdated: String = "never", val produce:T = FrijolDto() as T )
反序列化时保留@JvmSuppressWildcards避免类型擦除问题:
val ti= object: GenericTypeIndicator<TransactionDto<@JvmSuppressWildcards FrijolDto>>(){}
方案3:添加Firebase序列化注解
在ProduceDto接口及其实现类上添加@IgnoreExtraProperties,确保Firebase正确识别可序列化字段:
@IgnoreExtraProperties interface ProduceDto { fun someMethodCommonToAllProduce() } @IgnoreExtraProperties data class FrijolDto(...):ProduceDto { ... }
验证方法
修改后重新测试getFrijolesLots方法,确认LotDto<FrijolDto>能正常反序列化。新增农产品类型时,仅需在ProduceDtoConverter中添加对应类型分支,无需修改LotDto或UI逻辑,完美适配代码复用需求。
内容的提问来源于stack exchange,提问作者quealegriamasalegre
相关产品推荐
相关产品推荐

