使用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
相关产品推荐
相关产品推荐

