Java Spring Boot中使用Rest Client反序列化文本JSON问题
Spring Rest Client/Web Client 处理JSON字符串字面量响应问题
问题背景
调用外部REST API时,响应头Content-Type为application/json; charset=utf-8,但响应体并非标准JSON结构,而是JSON字符串字面量:
"{\"Code\":\"123-456\",\"Number\":1}"
而非预期的标准JSON:
{ "Code": "123-456", "Number": 1 }
直接使用Rest Client的两种方式均报错:
方式一:直接转换为实体类
ApiResponse response = restClient.post() .uri(url) .contentType(MediaType.APPLICATION_JSON) .body(request) .retrieve() .body(ApiResponse.class);
方式二:转换为String类型
String response = restClient.post() .uri(url) .contentType(MediaType.APPLICATION_JSON) .body(request) .retrieve() .body(String.class);
两种方式均在.body(...)环节抛出反序列化错误:
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对象,但实际响应以双引号开头,属于字符串字面量,导致解析逻辑冲突。
解决方案
方案1:Rest Client 手动处理响应体
先获取原始字节数组,手动去除首尾引号、还原转义字符后,再用ObjectMapper解析为实体:
// 构建默认配置的Rest Client RestClient restClient = RestClient.create(); // 获取原始响应字节 byte[] responseBytes = restClient.post() .uri(url) .contentType(MediaType.APPLICATION_JSON) .body(request) .retrieve() .body(byte[].class); // 处理字符串字面量:去除首尾引号,还原转义字符 String rawJson = new String(responseBytes, StandardCharsets.UTF_8) .replaceAll("^\"|\"$", "") .replace("\\\\", "\\"); // 解析为实体类 ApiResponse response = new ObjectMapper().readValue(rawJson, ApiResponse.class);
方案2:Rest Client 自定义消息转换器
通过自定义MappingJackson2HttpMessageConverter,统一处理这种特殊格式的响应:
// 自定义消息转换器 MappingJackson2HttpMessageConverter customConverter = new MappingJackson2HttpMessageConverter() { @Override protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage) throws IOException { String rawStr = StreamUtils.copyToString(inputMessage.getBody(), StandardCharsets.UTF_8); // 判断是否为字符串包裹的JSON if (rawStr.startsWith("\"") && rawStr.endsWith("\"")) { String processedJson = rawStr.substring(1, rawStr.length() - 1) .replace("\\\\", "\\"); return getObjectMapper().readValue(processedJson, clazz); } // 兼容正常JSON格式 return super.readInternal(clazz, inputMessage); } }; // 构建带自定义转换器的Rest Client RestClient restClient = RestClient.builder() .messageConverters(converters -> { // 移除默认的Jackson转换器 converters.removeIf(c -> c instanceof MappingJackson2HttpMessageConverter); // 添加自定义转换器 converters.add(customConverter); }) .build(); // 正常调用即可自动解析 ApiResponse response = restClient.post() .uri(url) .contentType(MediaType.APPLICATION_JSON) .body(request) .retrieve() .body(ApiResponse.class);
方案3:Web Client 异步处理响应
通过exchangeToMono手动处理响应体:
WebClient webClient = WebClient.create(); Mono<ApiResponse> responseMono = webClient.post() .uri(url) .contentType(MediaType.APPLICATION_JSON) .bodyValue(request) .exchangeToMono(clientResponse -> { return clientResponse.bodyToMono(String.class) .map(rawStr -> { String processedJson = rawStr.substring(1, rawStr.length() - 1) .replace("\\\\", "\\"); try { return new ObjectMapper().readValue(processedJson, ApiResponse.class); } catch (JsonProcessingException e) { throw new RuntimeException("解析响应失败", e); } }); }); // 同步获取结果(异步场景可使用subscribe) ApiResponse response = responseMono.block();
方案4:Web Client 自定义解码器
自定义Jackson2JsonDecoder统一处理特殊响应格式:
ObjectMapper objectMapper = new ObjectMapper(); Jackson2JsonDecoder customDecoder = new Jackson2JsonDecoder(objectMapper) { @Override public Object decode(DataBuffer dataBuffer, ResolvableType targetType, MimeType mimeType, Map<String, Object> hints) throws DecodingException { String rawStr = StandardCharsets.UTF_8.decode(dataBuffer.asByteBuffer()).toString(); if (rawStr.startsWith("\"") && rawStr.endsWith("\"")) { String processedJson = rawStr.substring(1, rawStr.length() - 1) .replace("\\\\", "\\"); try { return objectMapper.readValue(processedJson, targetType.getType()); } catch (JsonProcessingException e) { throw new DecodingException("解码包裹式JSON失败", e); } } return super.decode(dataBuffer, targetType, mimeType, hints); } }; // 构建带自定义解码器的Web Client WebClient webClient = WebClient.builder() .codecs(configurer -> configurer.customCodecs().register(customDecoder)) .build(); // 正常调用自动解析 ApiResponse response = webClient.post() .uri(url) .contentType(MediaType.APPLICATION_JSON) .bodyValue(request) .retrieve() .bodyToMono(ApiResponse.class) .block();
为何会出现这种返回格式?
这种非标准格式通常不是有意设计,而是以下原因导致的:
- 历史遗留问题:早期系统错误地将JSON对象序列化为字符串后返回,未修正
Content-Type或序列化逻辑 - 中间层处理失误:网关、代理等中间服务在转发响应时,额外对JSON做了字符串包裹处理
- 序列化配置错误:后端框架的序列化工具被错误配置,导致JSON对象先转为字符串再输出
- 旧客户端兼容:为适配只能处理字符串格式响应的旧客户端,后端被迫保留该格式
内容的提问来源于Stack Exchange,提问作者Gonzalo Martinez
相关产品推荐
相关产品推荐

