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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 23:28:04