我在处理来自 Spring 的外部 REST API 和 REST 客户端的响应时遇到问题。我目前正在使用新的(+3.2.0,如果我没记错的话)“Rest Client”,但我也会接受解决“Web Client”问题的答案,如果它适用并且您找不到新 Rest 客户端的答案。
简单地说,我向返回正文的外部 REST API 发出请求。内容类型标头内容为:
application/json; charset=utf-8
正文本身实际上并不是 Web 客户端或 Rest 客户端(我想)能够毫无问题地反序列化的普通 json。它似乎是一个文本 json,尽管该媒体类型本身并不存在。响应正文示例:
"{\"Code\":\"123-456\",\"Number\":1}"
让我重申,这是字面回应,而不是这个:
{
"Code": "123-456",
"Number": 1
}
我尝试过这个但没有成功:
{
Object response = restClient.post()
.uri(url)
.contentType(MediaType.APPLICATION_JSON)
.body(request)
.retrieve()
.body(ApiResponse.class);
}
我也尝试过:
{
Object response = restClient.post()
.uri(url)
.contentType(MediaType.APPLICATION_JSON)
.body(request)
.retrieve()
.body(String.class);
}
这两个都会抛出相同的错误,它们在
.body(...) part
处中断。我也尝试过.toEntity(...)
,它也在那里破裂。我只展示第一个:
Error while extracting response for type [com.example.test.models.ApiResponse] and content type [application/json;charset=utf-8]
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type `java.lang.String` from Object value (token `JsonToken.START_OBJECT`)
at [Source: (jdk.internal.net.http.ResponseSubscribers$HttpResponseInputStream); line: 1, column: 1]
我的猜测是,因为响应的内容类型标头是
application/json
,所以它需要一个以 {
开头的 json。然而,响应以 "
开头,因为就像我说的,它不是一个普通的 json,它是字符串文字格式的 json。因此Cannot deserialize value of type 'java.lang.String' from Object value (token 'JsonToken.START_OBJECT')
。
我设法将其更改为“Rest Template”并执行其他操作,但我不想这样做。我想通过 Rest 客户端/Web 客户端来实现此功能。
请帮忙,我不知道还能做什么。另外,有人故意这样做有什么理由吗?我的意思是,使响应成为 json 字符串而不是普通的 json。如果这个外部 REST API 以正常的 json 响应(我猜),我现在就不会写这个。
编辑:我使用默认的 Rest 客户端和默认的消息转换器。
编辑2:我调查了一下,我知道是什么引发了这个错误。我将把它留在下面的答案中。然而,仍然存在一个问题,与问题的标题更加一致。我也会把它留在那里。
编辑3:我在另一个问题中关注这个:Deserializing Json from String with Rest Client and Object Mapper
我还没能尝试解决方案,但它可能会给你一些想法:
您尝试过使用
bodyToMono(String.class)
来代替吗?即使一般不推荐,您也可以通过.block()
关注它。
然后使用 jackson 将你的responseString转换为json:
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import reactor.core.publisher.Mono;
// ...
String responseString = restClient.post()
.uri(url)
.contentType(MediaType.APPLICATION_JSON)
.body(request)
.retrieve()
.bodyToMono(String.class)
.block();
ObjectMapper objectMapper = new ObjectMapper();
JsonNode responseJson = objectMapper.readTree(responseString);
因此,这个外部 API 返回的是 500 而不是 200。这触发了我拥有的错误处理程序,但我不知道。问题是我能够在错误处理程序中检查错误消息。这样,我就能够解决第一个问题了。
事实证明,这个外部 API 期望请求正文是一个 string,而不是 object。
预计是这样的:
String responseString = restClient.post()
.uri(url)
.contentType(MediaType.APPLICATION_JSON)
.body(request.toJsonString()) // <-- method to convert it to a json string that I created
//...
不是这个:
String responseString = restClient.post()
.uri(url)
.contentType(MediaType.APPLICATION_JSON)
.body(request) // <-- original object from ApiRequest class
//...
所以,是的,很抱歉浪费了你的时间,该死的这个API,文档很少,而且有一些有问题的设计选择。
但是现在我有另一个问题,而且它与标题问题更相关。现在使用这段代码:
Object response = restClient.post()
.uri(url)
.contentType(MediaType.APPLICATION_JSON)
.body(request.toJsonString())
.retrieve()
.body(ApiResponse.class);
在
.body(...)
部分我遇到以下异常:
org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot construct instance of `com.example.test.models.ApiResponse` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('{"Code":"123-456","Number":1}')
ApiResponse 类如下所示:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ApiResponse {
@JsonProperty("Code")
private String code;
@JsonProperty("Number")
private String number;
}
外部 API 响应的内容是字面意思(根据 Postman 的说法):
"{\"Code\":\"123-456\",\"Number\":1}"
我错过了什么吗?