Retrofit自定义列表类型ConverterFactory异常问题排查
咱们先把问题的核心拆解开,一步步说清楚为什么你的转换器会失效,以及怎么修复。
为什么会出现这些问题?
1. 不加判断时verify()返回null
你的自定义转换器没有做任何过滤,会拦截所有API请求的ResponseBody。当verify()调用时,转换器会强行把它的响应(Verified结构的JSON对象)尝试转成ApiResponse——这显然结构不匹配,导致delegate.convert(value)解析失败得到null,最后返回envelope.items自然也是null。
2. 加了if (type != ApiResponse.class) return null;后callapi()报错
当你调用Call<List<Item>> callapi();时,Retrofit传入的type参数是List<Item>的类型,不是ApiResponse.class,所以你的转换器直接返回null,让Retrofit去用下一个默认转换器(比如GsonConverter)处理。但默认转换器拿到的是原始的ApiResponse JSON(一个对象),而你期望的返回类型是List<Item>(数组结构),自然就会抛出Expected BEGIN_ARRAY but was BEGIN_OBJECT的错误。
关于Retrofit转换器的Fallback逻辑
你可能误以为“转换器无法解析时会自动Fallback”,但实际逻辑是:只有当转换器的responseBodyConverter方法返回null时,Retrofit才会尝试下一个转换器。如果你的转换器返回了一个Converter实例(哪怕这个实例在转换时会失败),Retrofit就不会再走后面的转换器了。
正确的修复方案:用自定义注解标记需要拆包的API
最灵活且清晰的方式是给需要拆包的API方法加一个自定义注解,让转换器只处理这些请求,其他请求交给默认转换器处理。
步骤1:定义自定义注解
@Target(METHOD) @Retention(RUNTIME) public @interface UnwrapEnvelope { }
步骤2:在API接口中标记需要拆包的方法
// 给需要提取items的方法加上注解 @UnwrapEnvelope Call<List<Item>> callapi(); // verify方法不需要注解,交给默认转换器处理 Call<Verified> verify();
步骤3:修改自定义转换器
@Override public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) { // 检查当前API方法是否带有UnwrapEnvelope注解 boolean needUnwrap = false; for (Annotation annotation : annotations) { if (annotation instanceof UnwrapEnvelope) { needUnwrap = true; break; } } if (!needUnwrap) { // 不需要拆包,返回null让下一个转换器处理 return null; } // 需要拆包,先获取能解析ApiResponse的转换器 final Converter<ResponseBody, ApiResponse> delegate = retrofit.nextResponseBodyConverter(this, ApiResponse.class, annotations); // 返回自定义的转换器,提取items并返回 return value -> { ApiResponse envelope = delegate.convert(value); // 加个非空判断,避免空指针 return envelope != null ? envelope.items : null; }; }
其他可选方案(不够灵活,不推荐)
如果你不想用注解,也可以判断目标type是否是List<Item>(或者你期望的类型),但这种方式耦合性高,后续如果有其他需要拆包的API类型,还要修改转换器代码:
@Override public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) { // 判断目标类型是否是List<Item> if (!(type instanceof ParameterizedType) || ((ParameterizedType) type).getRawType() != List.class || ((ParameterizedType) type).getActualTypeArguments()[0] != Item.class) { return null; } // 剩下的逻辑和上面一致 final Converter<ResponseBody, ApiResponse> delegate = retrofit.nextResponseBodyConverter(this, ApiResponse.class, annotations); return value -> { ApiResponse envelope = delegate.convert(value); return envelope != null ? envelope.items : null; }; }
这样修改后,verify()会被默认转换器正确解析,callapi()会通过你的自定义转换器提取出items返回,就不会再出现问题了。
内容的提问来源于stack exchange,提问作者jL4

