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

Spring RestClient如何反序列化两种完全不同类型的第三方API响应

Spring RestClient如何反序列化两种完全不同类型的第三方API响应

这个第三方API的设计确实有点坑——不管成功失败都返回200,还返回完全不同的JSON结构,手动抓异常重试确实麻烦。下面给你几个Spring生态内的优雅解决方案,不用写try-catch就能自动适配两种响应类型:

方案1:先转成JsonNode,根据字段判断再转换

这是最直观的方案:先把响应体转成Jackson通用的JsonNode(不绑定到具体POJO),然后通过判断标志性字段的存在性(比如device或message),再决定转成GoodPojo还是BadPojo。

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.web.client.RestClient;

public String fetchApiResponse() {
    // 可以注入Spring管理的ObjectMapper,不需要手动new
    ObjectMapper objectMapper = new ObjectMapper();
    
    JsonNode responseNode = restClient.get()
        .uri("https://the-api.com")
        .retrieve()
        .body(JsonNode.class);

    Object result;
    if (responseNode.has("device")) {
        // 存在device字段,解析为成功响应
        result = objectMapper.treeToValue(responseNode, GoodPojo.class);
    } else if (responseNode.has("message")) {
        // 存在message字段,解析为错误响应
        result = objectMapper.treeToValue(responseNode, BadPojo.class);
    } else {
        // 处理未知结构的边界情况
        throw new RuntimeException("Unknown API response structure");
    }

    return result.toString();
}

这个方案完全避开了异常捕获,通过字段存在性直接判断响应类型,代码逻辑清晰,实现成本极低。

方案2:定义通用响应类(利用Jackson的灵活绑定)

如果想更贴合面向对象的写法,可以定义一个包含所有可能字段的通用响应类,利用Jackson的特性:当JSON中不存在某个字段时,对应的属性会被设为null,再通过字段是否为null来区分响应类型。

首先定义通用响应类:

public record GenericResponse(
    String device,
    Float temperature,
    String message,
    Integer code
) {
    // 提供判断响应类型的方法
    public boolean isSuccess() {
        return device != null;
    }

    // 转成具体POJO的方法
    public GoodPojo toGoodPojo() {
        return new GoodPojo(device, temperature);
    }

    public BadPojo toBadPojo() {
        return new BadPojo(message, code);
    }
}

然后直接调用API并转换:

public String fetchApiResponse() {
    GenericResponse genericResponse = restClient.get()
        .uri("https://the-api.com")
        .retrieve()
        .body(GenericResponse.class);

    Object result = genericResponse.isSuccess() 
        ? genericResponse.toGoodPojo() 
        : genericResponse.toBadPojo();

    return result.toString();
}

这个方案把类型判断的逻辑封装在通用类中,代码更整洁,适合需要在多处复用响应处理逻辑的场景。

方案3:自定义Jackson类型解析器(全局自动适配)

如果需要在多个地方处理这种多类型响应,可以通过自定义Jackson的类型解析逻辑,让框架自动帮你判断并转换类型。这个方案实现复杂,但一旦配置好就可以全局复用。

步骤1:定义标记接口

让两个POJO实现同一个空接口,作为Jackson的类型判断入口:

public interface ApiResponse {}

public record GoodPojo(String device, float temperature) implements ApiResponse {}
public record BadPojo(String message, int code) implements ApiResponse {}

步骤2:自定义类型解析器

实现Jackson的TypeIdResolver和TypeDeserializer,根据JSON字段判断目标类型:

import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.JsonParser;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
import com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer;
import com.fasterxml.jackson.databind.jsontype.impl.StdTypeResolverBuilder;
import com.fasterxml.jackson.databind.jsontype.impl.TypeIdResolverBase;
import com.fasterxml.jackson.databind.type.JavaType;
import com.fasterxml.jackson.databind.type.TypeFactory;
import java.io.IOException;

public class ApiResponseTypeIdResolver extends TypeIdResolverBase {
    private JavaType baseType;

    @Override
    public void init(JavaType baseType) {
        this.baseType = baseType;
    }

    @Override
    public String idFromValue(Object value) {
        return idFromValueAndType(value, value.getClass());
    }

    @Override
    public String idFromValueAndType(Object value, Class<?> suggestedType) {
        return (value instanceof GoodPojo) ? "good" : "bad";
    }

    @Override
    public JavaType typeFromId(DeserializationContext context, String id) {
        return "good".equals(id) 
            ? context.constructType(GoodPojo.class) 
            : context.constructType(BadPojo.class);
    }

    @Override
    public JsonTypeInfo.Id getMechanism() {
        return JsonTypeInfo.Id.CUSTOM;
    }
}

public class ApiResponseTypeResolverBuilder extends StdTypeResolverBuilder {
    @Override
    public TypeDeserializer buildTypeDeserializer(DeserializationConfig config, JavaType baseType, Collection<NamedType> subtypes) {
        return new ApiResponseTypeDeserializer(baseType, this, null, "_type", false, null, config.getTypeFactory());
    }

    private static class ApiResponseTypeDeserializer extends AsPropertyTypeDeserializer {
        public ApiResponseTypeDeserializer(JavaType baseType, TypeResolverBuilder<?> builder, TypeIdResolver idRes, String typePropertyName, boolean typeIdVisible, Class<?> defaultImpl, TypeFactory typeFactory) {
            super(baseType, builder, idRes, typePropertyName, typeIdVisible, defaultImpl, typeFactory);
        }

        @Override
        public Object deserializeTypedFromObject(JsonParser p, DeserializationContext ctxt) throws IOException {
            JsonNode node = p.readValueAsTree();
            // 根据字段存在性判断类型ID
            String typeId = node.has("device") ? "good" : "bad";
            // 重置解析器位置,让Jackson继续处理
            p = ctxt.getParserFactory().createParser(node.toString());
            p.nextToken();
            return super.deserializeTypedFromObject(p, ctxt);
        }
    }
}

步骤3:给标记接口添加Jackson注解

import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.annotation.JsonTypeResolver;

@JsonTypeInfo(use = JsonTypeInfo.Id.CUSTOM, resolver = ApiResponseTypeIdResolver.class)
@JsonTypeResolver(ApiResponseTypeResolverBuilder.class)
public interface ApiResponse {}

步骤4:直接调用API

现在可以直接把响应体转成ApiResponse类型,Jackson会自动帮你判断并转换:

public String fetchApiResponse() {
    ApiResponse result = restClient.get()
        .uri("https://the-api.com")
        .retrieve()
        .body(ApiResponse.class);

    return result.toString();
}

这个方案实现了完全自动化的类型转换,但配置成本最高,适合需要在全局范围内处理多类型响应的场景。

方案选择建议

  • 简单场景优先选方案1:代码量少,逻辑直观,不需要额外封装。
  • 多场景复用优先选方案2:面向对象封装,代码可读性更高。
  • 全局统一处理优先选方案3:一劳永逸,适合大型项目的统一API响应处理。

所有方案都不需要手动编写try-catch捕获转换异常,完全符合你的需求。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.07 08:43:11