一、问题背景
在 RocketMQ 消费消息时,默认会将消息内容转换成 String 类型 进行传输。原本是一个map结构,接收是String 类型导致ENGINE_RESPONSE有很多转义字符。阿里云的RocketMQ消息,内容如下:
{
"BUSINESS_ID": 12,
"result": "PASS",
"CUSTOMER_ID": 123456,
"type": "A",
"ENGINE_RESPONSE": "{\"data\":{\"ac\":969,\"bd\":0.06,\"type\":\"A\",\"score\":768.0,\"amt\":1,\"choice\":\"3,6\",\"cardType\":\"test\",\"eventID\":\"db4c3ae6-4114-4ab8-8bd4-a00b9aeb3c47\",\"orderID\":12}}"
}
消息的ENGINE_RESPONSE字段,内部嵌套了一个JSON的字符串,而不是直接的JSON对象。直接用ObjectMapper 反序列化时进行反序列化解析的时候会报类型不匹配解析失败,导致消费失败。
二、问题原因
实体类:
@Data
public class RiskMessage {
@JsonProperty("BUSINESS_ID")
private Long businessId;
private String result;
@JsonProperty("CUSTOMER_ID")
private Long customerId;
private String type;
@JsonProperty("ENGINE_RESPONSE")
private EngineResponse engineResponse;
}
错误:
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of com.yinshan.arkham.risk.RiskResponse$EngineResponse (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value
Jackson 试图将字符串 ENGINE_RESPONSE 直接解析为 EngineResponse 对象,但没有适用于字符串的构造方法或工厂方法,导致解析失败。
三、思考与采取措施
Jackson 认为 ENGINE_RESPONSE 是一个普通的 String,但我们的 DTO 里将它映射为 EngineResponse 对象,类型不匹配。
问题分析
Jackson 认为 ENGINE_RESPONSE
是 String
,但 DTO 里是 EngineResponse
,类型不匹配。还带转义字符。
思考过程
- 方法 1: 手动去除转义字符
错误说是String类型,修改类型 EngineResponse 为String ,还要用到其中的其他字段,所以在getEngineResponse里面,先去掉转义字符,然后在用ObjectMapper 解析。 - String EngineResponse
- 方法 2:使用 Jackson 自定义反序列化器
自定义反序列化器 这样就可以直接用EngineResponse类型
四、解决方案
方法 1:手动去除转义字符,在 getEngineResponse() 方法中处理
@JsonProperty("ENGINE_RESPONSE")
private String engineResponseJson; // 原始数据
private EngineResponse engineResponse; // 实际使用的对象
public EngineResponse getEngineResponse() {
if (engineResponse == null && engineResponseJson != null) {
try {
String cleanJson = engineResponseJson.replaceAll("\\\\\"", "\"");
ObjectMapper objectMapper = new ObjectMapper();
this.engineResponse = objectMapper.readValue(cleanJson, EngineResponse.class);
} catch (Exception e) {
throw new RuntimeException("Failed to parse ENGINE_RESPONSE", e);
}
}
return engineResponse;
}
方法 2:使用 Jackson 自定义反序列化器
直接在 ENGINE_RESPONSE 这个字段上解析 JSON,而不是手动处理转义字符
public class EngineResponseDeserializer extends JsonDeserializer<RiskResponse.EngineResponse> {
@Override
public RiskResponse.EngineResponse deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
ObjectMapper objectMapper = (ObjectMapper) p.getCodec();
String jsonString = p.getText();
return objectMapper.readValue(jsonString, RiskResponse.EngineResponse.class);
}
}
@JsonProperty("ENGINE_RESPONSE")
@JsonDeserialize(using = EngineResponseDeserializer.class)
private EngineResponse engineResponse;
五、总结
问题的关键在于消息体中某些字段是嵌套的 JSON 字符串,而不是直接的 JSON 对象。
对比两种方法