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

Android项目中泛型类型的Moshi Adapter使用问题

解决Android中Moshi泛型多态解析的问题

嘿,我刚好之前处理过类似的Moshi解析场景,给你一套完整的解决方案!你的核心问题是ScheduleItem里的type字段用来标识item的具体类型(Event/Game/CheckIn),而且还有嵌套的schedule_item_groups,这种泛型多态场景Moshi默认没法自动处理,得针对性适配。

1. 先明确数据类结构

首先确保你的数据类定义清晰,这里我用Kotlin示例:

// 外层分组
data class ScheduleGroups(
    val label: String,
    val schedule_items: List<ScheduleItem>
)

// 单个日程项(先使用Any接收多态的item,后续优化类型安全)
data class ScheduleItem(
    val type: String,
    val item: Any,
    val schedule_item_groups: List<ScheduleGroups>? = null
)

// 具体的item类型
data class Event(val eventId: String, val name: String)
data class Game(val gameId: String, val score: Int)
data class CheckIn(val checkInId: String, val time: String)

2. 自定义ScheduleItem的JsonAdapter

因为type字段属于ScheduleItem本身,而非item对象内部,Moshi自带的PolymorphicJsonAdapterFactory没法直接处理这种父对象存判别字段的场景,所以我们要手动写Adapter:

class ScheduleItemAdapter(private val moshi: Moshi) : JsonAdapter<ScheduleItem>() {
    // 提前初始化各类型的Adapter
    private val eventAdapter = moshi.adapter(Event::class.java)
    private val gameAdapter = moshi.adapter(Game::class.java)
    private val checkInAdapter = moshi.adapter(CheckIn::class.java)
    private val groupsAdapter = moshi.adapter<List<ScheduleGroups>>()

    override fun fromJson(reader: JsonReader): ScheduleItem? {
        reader.beginObject()
        var type: String? = null
        var item: Any? = null
        var groups: List<ScheduleGroups>? = null

        while (reader.hasNext()) {
            when (reader.nextName()) {
                "type" -> type = reader.nextString()
                "item" -> {
                    type?.let {
                        item = when (it) {
                            "event" -> eventAdapter.fromJson(reader)
                            "game" -> gameAdapter.fromJson(reader)
                            "checkin" -> checkInAdapter.fromJson(reader)
                            else -> throw JsonDataException("未知的item类型: $it")
                        }
                    } ?: throw JsonDataException("item字段前必须先指定type")
                }
                "schedule_item_groups" -> groups = groupsAdapter.fromJson(reader)
                else -> reader.skipValue() // 跳过未知字段
            }
        }
        reader.endObject()

        return ScheduleItem(
            type = requireNotNull(type) { "type字段不能为空" },
            item = requireNotNull(item) { "type为$type时,item字段不能为空" },
            schedule_item_groups = groups
        )
    }

    override fun toJson(writer: JsonWriter, value: ScheduleItem?) {
        if (value == null) {
            writer.nullValue()
            return
        }
        writer.beginObject()
        writer.name("type").value(value.type)
        writer.name("item")
        // 根据item实际类型序列化
        when (value.item) {
            is Event -> eventAdapter.toJson(writer, value.item)
            is Game -> gameAdapter.toJson(writer, value.item)
            is CheckIn -> checkInAdapter.toJson(writer, value.item)
            else -> throw JsonDataException("无法识别的item类型: ${value.item::class.java}")
        }
        // 序列化嵌套分组(如果存在)
        value.schedule_item_groups?.let {
            writer.name("schedule_item_groups")
            groupsAdapter.toJson(writer, it)
        }
        writer.endObject()
    }
}

3. 注册Adapter并使用Moshi

创建Moshi实例时,把自定义的Adapter注册进去:

val moshi = Moshi.Builder()
    .add(ScheduleItemAdapter::class.java)
    .build()

// 解析JSON示例
val scheduleAdapter = moshi.adapter<List<ScheduleGroups>>()
val scheduleList = scheduleAdapter.fromJson(你的JSON字符串)

4. 优化:用密封类实现类型安全

如果不想用Any接收item(避免强转),可以把ScheduleItem改成密封类,让类型更安全:

sealed class ScheduleItem {
    abstract val type: String
    abstract val schedule_item_groups: List<ScheduleGroups>?

    data class EventItem(
        override val type: String = "event",
        val item: Event,
        override val schedule_item_groups: List<ScheduleGroups>? = null
    ) : ScheduleItem()

    data class GameItem(
        override val type: String = "game",
        val item: Game,
        override val schedule_item_groups: List<ScheduleGroups>? = null
    ) : ScheduleItem()

    data class CheckInItem(
        override val type: String = "checkin",
        val item: CheckIn,
        override val schedule_item_groups: List<ScheduleGroups>? = null
    ) : ScheduleItem()
}

对应的Adapter里的fromJson方法也要调整,返回对应的密封类实例,这样使用时就可以用when表达式安全处理,不用强转:

// Adapter的fromJson部分调整
return when (type) {
    "event" -> ScheduleItem.EventItem(
        item = requireNotNull(eventAdapter.fromJson(reader)),
        schedule_item_groups = groups
    )
    "game" -> ScheduleItem.GameItem(
        item = requireNotNull(gameAdapter.fromJson(reader)),
        schedule_item_groups = groups
    )
    "checkin" -> ScheduleItem.CheckInItem(
        item = requireNotNull(checkInAdapter.fromJson(reader)),
        schedule_item_groups = groups
    )
    else -> throw JsonDataException("未知的item类型: $type")
}

使用示例:

scheduleList?.forEach { group ->
    group.schedule_items.forEach { item ->
        when (item) {
            is ScheduleItem.EventItem -> println("事件:${item.item.name}")
            is ScheduleItem.GameItem -> println("游戏得分:${item.item.score}")
            is ScheduleItem.CheckInItem -> println("签到时间:${item.item.time}")
        }
    }
}

内容的提问来源于stack exchange,提问作者jordond

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 09:27:38