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

自定义Moshi JsonAdapter跳过坏元素触发AssertionError问题排查

问题分析

你的自定义适配器核心问题出在peekJson()的使用上。Moshi的JsonUtf8Reader.peekJson()方法要求底层数据源是可重复读取的(比如内存中的字节数组),但Retrofit使用的ResponseBody数据源是一次性流——它不支持回退或重复读取未消费的数据。

当你调用peekJson()时,Moshi会尝试复制当前reader的缓冲区状态,但如果原数据源无法提供足够的重复数据(比如网络流已被部分消费),就会触发IOException,而Moshi内部把这个异常包装成了AssertionError,这就是你看到崩溃栈的根源。

修正方案

你不需要用peekJson()预读元素,直接在try块里尝试解析当前元素即可。如果解析失败,再调用skipValue()跳过坏元素,这样就能避免peekJson带来的数据源重复读取问题。修正后的代码如下:

class SkipBadElementsListAdapter(private val elementAdapter: JsonAdapter<Any?>) : JsonAdapter<List<Any?>>() {
    object Factory : JsonAdapter.Factory {
        override fun create(
            type: Type, annotations: Set<Annotation>, moshi: Moshi
        ): JsonAdapter<*>? {
            if (annotations.isNotEmpty() || Types.getRawType(type) != List::class.java) {
                return null
            }
            val elementType = Types.collectionElementType(type, List::class.java)
            val elementAdapter = moshi.adapter<Any?>(elementType)
            return SkipBadElementsListAdapter(elementAdapter).nullSafe()
        }
    }

    override fun fromJson(reader: JsonReader): List<Any?>? {
        val result = mutableListOf<Any?>()
        reader.beginArray()
        while (reader.hasNext()) {
            try {
                // 直接使用原reader解析,无需peekJson
                val element = elementAdapter.fromJson(reader)
                result += element
            } catch (ignored: JsonDataException) {
                Timber.tag("JsonParseError").d(ignored.toString())
                // 解析失败时手动跳过当前坏元素
                reader.skipValue()
            }
        }
        reader.endArray()
        return result
    }

    override fun toJson(writer: JsonWriter, value: List<Any?>?) {
        if (value == null) {
            throw NullPointerException("value was null! Wrap in .nullSafe() to write nullable values.")
        }
        writer.beginArray()
        for (i in value.indices) {
            elementAdapter.toJson(writer, value[i])
        }
        writer.endArray()
    }
}
为什么这样能解决问题?
  • 直接用elementAdapter.fromJson(reader)会消费当前元素的JSON数据:如果解析成功,reader会自动移动到下一个元素;如果失败,我们调用skipValue()手动跳过当前坏元素,确保reader的位置始终正确。
  • 整个过程没有尝试复制数据源或重复读取,完全适配Retrofit的一次性流场景,从根本上避免了AssertionError的触发。

内容的提问来源于stack exchange,提问作者h.kelidari

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 09:07:43