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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 04:32:58