
本文详解如何在 Java 中使用 Jackson 正确反序列化含嵌套 JSON 字符串(即 JSON 值本身是转义后的 JSON 字符串)的响应,重点解决因字符串转义不足导致的 Unexpected character 解析异常。
本文详解如何在 java 中使用 jackson 正确反序列化含嵌套 json 字符串(即 json 值本身是转义后的 json 字符串)的响应,重点解决因字符串转义不足导致的 `unexpected character` 解析异常。
在实际微服务调用或网关透传场景中,常会遇到如下结构的 JSON 响应:
{
"errormessage": "{"timestamp":"2021-10-19T07:57:35.205+0000","status":400,"error":"Bad Request","message":"Bad Request: ... path=/xxx/verify","path":"/xxx/xxx"}"
}注意:errormessage 的值不是普通字符串,而是一个被双引号包裹、且内部所有双引号和反斜杠均已 JSON 转义的字符串——即它是一个「JSON-encoded JSON string」。Jackson 默认会尝试将该字段直接解析为对象,但若未做特殊处理,就会在解析到内部 {"timestamp":...} 的开头 { 时失败,报错:
Unexpected character ('t' (code 116)): was expecting comma to separate Object entries这是因为 Jackson 将 errormessage 字段误判为一个 JSON 对象的开始,而实际上它应被当作 原始字符串(raw string) 先完整读取,后续再由开发者决定是否二次解析。
✅ 正确做法:声明字段为 String 并禁用自动嵌套解析
首先,确保 TransferResponse 类中 errormessage 对应字段类型为 String(而非嵌套对象),并使用 @JsonAlias 支持大小写变体:
public class TransferResponse {
// 注意:此处不定义嵌套对象字段,仅保留原始字符串
@JsonAlias({"errorMessage", "errormessage"})
private String errormessage; // ← 关键:必须是 String 类型!
// 若需访问嵌套内容,可提供 getter + 手动解析逻辑(见后文)
public String getErrormessage() {
return errormessage;
}
// 其他字段(如 timeStamp 等)暂不映射,除非你确定 errormessage 总是标准格式
}✅ 构造测试字符串时:严格遵循 Java 字符串转义规则
当你在代码中硬编码嵌套 JSON 字符串(例如单元测试),必须对内部 JSON 的每个双引号 " 和反斜杠 进行 Java 层转义:
| JSON 原始内容 | 在 Java 字符串中应写作 |
|---|---|
| "timestamp":"..." | \"timestamp\":\"...\" |
| {"key":"val"} | "{\"key\":\"val\"}" |
因此,正确的测试字符串应为:
final String nestedErrorResponse =
"{"errormessage":"{\"timestamp\":\"2021-10-19T07:57:35.205+0000\",\"status\":400,\"error\":\"Bad Request\",\"message\":\"Bad Request: xxxx xx is xx xxx. path=/xxx/verify\",\"path\":\"/xxx/xxx\"}"}";? 提示:IntelliJ IDEA 在粘贴 JSON 到字符串字面量中时,会自动添加必要转义(右键 → Paste as JSON 或启用 Smart Paste),大幅降低出错概率。
✅ 安全反序列化:使用标准 ObjectMapper 配置
无需额外模块即可完成基础解析(Jdk8Module 对 Optional 有用,但本例中建议先用 String):
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new Jdk8Module()); // 若仍需 Optional 支持
TransferResponse response = mapper.readValue(nestedErrorResponse, TransferResponse.class);
System.out.println("Raw errormessage: " + response.getErrormessage());
// 输出:{"timestamp":"2021-10-19T07:57:35.205+0000", ... }? 进阶:按需解析嵌套 JSON(推荐封装)
若需进一步提取 timestamp、status 等字段,建议封装解析逻辑,避免污染主 POJO:
public class ErrorResponseDetails {
private String timestamp;
private int status;
private String error;
private String message;
private String path;
// getters/setters...
}
// 在 TransferResponse 中添加辅助方法:
public Optional<ErrorResponseDetails> parseErrorMessage() {
if (errormessage == null || errormessage.trim().isEmpty()) {
return Optional.empty();
}
try {
return Optional.of(new ObjectMapper().readValue(errormessage, ErrorResponseDetails.class));
} catch (JsonProcessingException e) {
// 记录 warn 日志:errormessage 格式异常
return Optional.empty();
}
}⚠️ 注意事项与最佳实践
- ❌ 不要试图用 @JsonUnwrapped 或自定义 JsonDeserializer 直接将 errormessage 映射为对象——这违背了其“字符串字段”的语义,易引发不可控异常;
- ✅ 始终优先将嵌套 JSON 字段建模为 String,再按业务需要选择性解析,保障健壮性;
- ✅ 生产环境建议配合 @JsonIgnoreProperties(ignoreUnknown = true) 防止未知字段导致失败;
- ✅ 若 API 文档明确 errormessage 恒为标准错误结构,可考虑统一定义 ErrorResponseDetails 并通过 @JsonCreator + @JsonProperty 实现单步解析(需自定义反序列化器),但复杂度上升,初学者慎用。
掌握这一模式,即可稳定处理网关、Mock Server、日志注入等场景中高频出现的「JSON-in-JSON」问题。










