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

Spring 3.0.0中RestTemplate无法反序列化JSON数组问题排查

Spring 3.0.0中RestTemplate获取Ingredient列表失败,但单个查询正常的问题

问题描述

在Spring 3.0.0环境下,使用RestTemplate调用批量查询接口http://localhost:8080/data-api/ingredients时抛出反序列化错误,但通过ID调用单个对象接口http://localhost:8080/data-api/ingredients/{id}时功能正常。

错误栈

Caused by: org.springframework.web.client.RestClientException: 提取类型为[class [Lru.sandwichcloud.Ingredient;]、内容类型为[application/json]的响应时出错
at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:118) ~[spring-web-6.0.2.jar!/:6.0.2]
at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:1132) ~[spring-web-6.0.2.jar!/:6.0.2]
at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:1115) ~[spring-web-6.0.2.jar!/:6.0.2]
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:865) ~[spring-web-6.0.2.jar!/:6.0.2]
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:764) ~[spring-web-6.0.2.jar!/:6.0.2]
at org.springframework.web.client.RestTemplate.getForEntity(RestTemplate.java:405) ~[spring-web-6.0.2.jar!/:6.0.2]
at ru.sandwichcloud.SandwichCloudClient.getAllIngredients(SandwichCloudClient.java:42) ~[classes!/:0.0.7-SNAPSHOT]
at ru.sandwichcloud.RestExamples.lambda$fetchIngredients$0(RestExamples.java:34) ~[classes!/:0.0.7-SNAPSHOT]
at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:767) ~[spring-boot-3.0.0.jar!/:3.0.0]
11 common frames omitted
Caused by: org.springframework.http.converter.HttpMessageNotReadableException: JSON解析错误:无法将对象值反序列化为类型[Lru.sandwichcloud.Ingredient;(token JsonToken.START_OBJECT)
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:406) ~[spring-web-6.0.2.jar!/:6.0.2]
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.read(AbstractJackson2HttpMessageConverter.java:354) ~[spring-web-6.0.2.jar!/:6.0.2]
at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:103) ~[spring-web-6.0.2.jar!/:6.0.2]
19 common frames omitted
Caused by: com.fasterxml.jackson.databind.exc.MismatchedInputException: 无法将对象值反序列化为类型[Lru.sandwichcloud.Ingredient;(token JsonToken.START_OBJECT)
at [Source: (org.springframework.util.StreamUtils$NonClosingInputStream); line: 1, column: 1]
at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:59) ~[jackson-databind-2.14.1.jar!/:2.14.1]
at com.fasterxml.jackson.databind.DeserializationContext.reportInputMismatch(DeserializationContext.java:1746) ~[jackson-databind-2.14.1.jar!/:2.14.1]
at com.fasterxml.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1520) ~[jackson-databind-2.14.1.jar!/:2.14.1]
at com.fasterxml.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1467) ~[jackson-databind-2.14.1.jar!/:2.14.1]
at com.fasterxml.jackson.databind.deser.std.ObjectArrayDeserializer.handleNonArray(ObjectArrayDeserializer.java:345) ~[jackson-databind-2.14.1.jar!/:2.14.1]
at com.fasterxml.jackson.databind.deser.std.ObjectArrayDeserializer.deserialize(ObjectArrayDeserializer.java:196) ~[jackson-databind-2.14.1.jar!/:2.14.1]
at com.fasterxml.jackson.databind.deser.std.ObjectArrayDeserializer.deserialize(ObjectArrayDeserializer.java:26) ~[jackson-databind-2.14.1.jar!/:2.14.1]
at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:323) ~[jackson-databind-2.14.1.jar!/:2.14.1]
at com.fasterxml.jackson.databind.ObjectReader._bindAndClose(ObjectReader.java:2105) ~[jackson-databind-2.14.1.jar!/:2.14.1]
at com.fasterxml.jackson.databind.ObjectReader.readValue(ObjectReader.java:1481) ~[jackson-databind-2.14.1.jar!/:2.14.1]
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:395) ~[spring-web-6.0.2.jar!/:6.0.2]
21 common frames omitted

批量接口返回的JSON结构

{
  "_embedded" : {
    "ingredients" : [ {
      "name" : "Dark Bread",
      "type" : "BREAD",
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/data-api/ingredients/DARK"
        },
        "ingredient" : {
          "href" : "http://localhost:8080/data-api/ingredients/DARK"
        }
      }
    }, {
      "name" : "White Bread",
      "type" : "BREAD",
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/data-api/ingredients/WHTE"
        },
        "ingredient" : {
          "href" : "http://localhost:8080/data-api/ingredients/WHTE"
        }
      }
    }, {
      "name" : "Beef",
      "type" : "PROTEIN",
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/data-api/ingredients/BEEF"
        },
        "ingredient" : {
          "href" : "http://localhost:8080/data-api/ingredients/BEEF"
        }
      }
    }, {
      "name" : "Mustard",
      "type" : "SAUCE",
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/data-api/ingredients/MTRD"
        },
        "ingredient" : {
          "href" : "http://localhost:8080/data-api/ingredients/MTRD"
        }
      }
    } ]
  },
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/data-api/ingredients"
    },
    "profile" : {
      "href" : "http://localhost:8080/data-api/profile/ingredients"
    }
  }
}

相关代码

批量获取列表的代码(出错版本)

public List<Ingredient> getAllIngredients() {
    return rest.exchange("http://localhost:8080/data-api/ingredients",
                    HttpMethod.GET,
                    null,
                    new ParameterizedTypeReference<List<Ingredient>>() {}
            ).getBody();
}

Ingredient实体类

@Data
@RequiredArgsConstructor
@NoArgsConstructor(access=AccessLevel.PRIVATE, force=true)
@Entity
public class Ingredient {

  @Id
  private final String id;
  private final String name;
  private final Type type;

  public enum Type {
    BREAD, PROTEIN, VEGGIES, CHEESE, SAUCE
  }

}

单个对象获取代码(正常运行)

public Ingredient getIngredientById(String ingredientId) {
    return rest.getForObject(
            "http://localhost:8080/data-api/ingredients/{id}",
            Ingredient.class,
            ingredientId);
}

错误原因

批量接口返回的是HAL(Hypertext Application Language)格式的JSON,最外层是一个包含_embedded和_links的对象,Ingredient列表嵌套在_embedded.ingredients路径下。而代码直接尝试将整个响应反序列化为List<Ingredient>,Jackson无法将外层对象转换成列表,因此抛出类型不匹配的反序列化错误。

单个对象接口可以正常工作,是因为其返回的JSON结构直接包含name、type等Ingredient类的字段,Jackson默认会忽略未知的_links字段,所以能成功反序列化。

解决方案

方案1:创建HAL格式的包装类(推荐)

定义一个包装类来匹配批量接口的JSON结构,再从中提取Ingredient列表:

@Data
public class IngredientWrapper {
    private Embedded _embedded;

    @Data
    public static class Embedded {
        private List<Ingredient> ingredients;
    }
}

修改批量获取方法:

public List<Ingredient> getAllIngredients() {
    ResponseEntity<IngredientWrapper> response = rest.exchange(
            "http://localhost:8080/data-api/ingredients",
            HttpMethod.GET,
            null,
            IngredientWrapper.class
    );
    return response.getBody().get_embedded().getIngredients();
}

方案2:使用JsonNode手动解析(无需新增类)

如果不想创建包装类,可以先将响应读取为字符串,再通过Jackson的JsonNode解析提取列表:

public List<Ingredient> getAllIngredients() {
    ResponseEntity<String> response = rest.exchange(
            "http://localhost:8080/data-api/ingredients",
            HttpMethod.GET,
            null,
            String.class
    );
    ObjectMapper mapper = new ObjectMapper();
    try {
        JsonNode root = mapper.readTree(response.getBody());
        JsonNode ingredientsNode = root.path("_embedded").path("ingredients");
        return mapper.readValue(ingredientsNode.toString(), new TypeReference<List<Ingredient>>() {});
    } catch (IOException e) {
        throw new RuntimeException("解析Ingredient列表失败", e);
    }
}

内容的提问来源于stack exchange,提问作者tsavaph

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 20:45:22