自定义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
相关产品推荐
相关产品推荐

