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

使用Moshi处理含重复events字段的异构JSON响应问题

我来帮你搞定这个Moshi适配的棘手问题!后端返回两种不一致的JSON格式确实挺闹心,但我们可以通过自定义JsonAdapter完美兼容这两种场景,把它们都解析成你需要的List<Event>结构。

首先先明确下两种需要兼容的JSON格式:

格式1(events为数组):

{
  "events": [
    {"id": 1, "name": "Event A"},
    {"id": 2, "name": "Event B"}
  ]
}

格式2(多个重复的events字段):

{
  "events": {"id": 1, "name": "Event A"},
  "events": {"id": 2, "name": "Event B"}
}

问题根源

Moshi默认会把重复的字段判定为错误(抛出JsonDataException),同时它也无法自动识别单个对象和数组的差异,所以我们需要自己写适配器来接管这部分解析逻辑。

解决方案

1. 定义Parcelable数据类

先把你的目标数据类写好,确保是Parcelable的:

@Parcelize
data class EventResponse(
    val events: List<Event>
) : Parcelable

@Parcelize
data class Event(
    val id: Int,
    val name: String
    // 这里根据你的实际字段补充
) : Parcelable

2. 编写自定义JsonAdapter

这个适配器的核心逻辑是:遍历JSON对象的所有键值对,收集所有events字段的值——不管它是单个对象还是数组,最终都合并到一个列表里。

用@FromJson/@ToJson注解的写法更简洁,Moshi会自动帮我们注入Event的适配器:

class EventResponseAdapter {

    @FromJson
    fun fromJson(reader: JsonReader, eventAdapter: JsonAdapter<Event>): EventResponse {
        val eventList = mutableListOf<Event>()
        
        reader.beginObject()
        while (reader.hasNext()) {
            when (reader.nextName()) {
                "events" -> {
                    // 判断当前events字段的类型:数组或单个对象
                    when (reader.peek()) {
                        JsonReader.Token.BEGIN_ARRAY -> {
                            // 解析数组格式的events
                            reader.beginArray()
                            while (reader.hasNext()) {
                                eventAdapter.fromJson(reader)?.let { eventList.add(it) }
                            }
                            reader.endArray()
                        }
                        JsonReader.Token.BEGIN_OBJECT -> {
                            // 解析单个对象格式的events(重复字段会多次走到这里)
                            eventAdapter.fromJson(reader)?.let { eventList.add(it) }
                        }
                        else -> {
                            // 跳过不合法的字段类型,避免崩溃
                            reader.skipValue()
                        }
                    }
                }
                else -> {
                    // 跳过其他无关字段
                    reader.skipValue()
                }
            }
        }
        reader.endObject()
        
        return EventResponse(eventList)
    }

    @ToJson
    fun toJson(writer: JsonWriter, response: EventResponse, eventAdapter: JsonAdapter<Event>) {
        // 如果需要序列化,统一按数组格式输出即可
        writer.beginObject()
        writer.name("events")
        writer.beginArray()
        response.events.forEach { eventAdapter.toJson(writer, it) }
        writer.endArray()
        writer.endObject()
    }
}

3. 注册适配器到Moshi+Retrofit

把自定义适配器注册到Moshi实例,再结合Retrofit使用:

// 构建Moshi实例,注册自定义适配器
val moshi = Moshi.Builder()
    .add(EventResponseAdapter())
    .build()

// 构建Retrofit
val retrofit = Retrofit.Builder()
    .baseUrl("你的接口基础地址")
    .addConverterFactory(MoshiConverterFactory.create(moshi))
    .build()

// 定义API接口
interface EventApi {
    @GET("events")
    suspend fun getEvents(): Response<EventResponse>
}

工作原理说明

这个适配器会逐个读取JSON对象的键值对:

  • 遇到events字段时,先判断它的类型:如果是数组就遍历解析每个元素;如果是单个对象就直接解析并加入列表(重复的events字段会多次触发这个逻辑,自然就把所有单个Event都收集起来了)
  • 自动跳过其他无关字段,同时处理不合法的字段类型防止崩溃
  • 最终不管后端返回哪种格式,都会统一转换成EventResponse(List<Event>)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 09:03:30