结论:自定义JsonSerializer用于控制序列化输出形态,无法解决嵌套JSON解析问题;解析嵌套JSON字符串必须使用JsonDeserializer或JsonNode手动处理,因JsonSerializer仅作用于Java对象→JSON字符串的单向序列化,不参与反序列化流程。

直接说结论:自定义 JsonSerializer 不是用来“解析”嵌套 JSON 的,而是用来**控制序列化输出形态**的;如果你卡在“解析”(即反序列化)阶段,比如要从一个字段值是 JSON 字符串的结构里提取嵌套对象,那真正该用的是 JsonDeserializer 或 JsonNode 手动处理——混淆这两者,90% 的人会白调半天。
为什么用 JsonSerializer 解决不了“解析嵌套 JSON”的问题
JsonSerializer 只在 Java 对象 → JSON 字符串 这一方向起作用。它不碰输入、不读原始 JSON、不参与反序列化流程。常见误用场景是:后端收到一个字段 "config": "{\"timeout\":30,\"retries\":2}",想把它自动转成 Config 对象,却去写 JsonSerializer<config></config> ——这完全走反了方向。
这类需求本质是“字符串内嵌 JSON 的二次反序列化”,必须靠反向操作:
- 要么注册
JsonDeserializer<config></config>,在deserialize()里用objectMapper.readTree()先解析字符串,再用treeToValue()转对象 - 要么把字段类型声明为
JsonNode,由上层业务手动调用objectMapper.treeToValue(node, Config.class) - 若字段结构固定且简单,也可用
@JsonCreator+@JsonProperty构造器注入原始字符串,自行解析
JsonDeserializer 处理嵌套 JSON 字符串的典型写法
假设收到的 JSON 是:
{"id":123,"meta":"{\"user_id\":\"u789\",\"tags\":[\"a\",\"b\"]}"}
而你希望 meta 字段直接反序列化为 Meta 对象,不是 String。这时要写:
1. 定义 Meta 类(带无参构造器 + getter/setter)
2. 实现 JsonDeserializer<meta>:
public class MetaDeserializer extends JsonDeserializer<Meta> {
@Override
public Meta deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException {
String jsonStr = p.getText(); // 读出原始字符串值
if (jsonStr == null || jsonStr.trim().isEmpty()) {
return null;
}
try {
return new ObjectMapper().readValue(jsonStr, Meta.class);
} catch (JsonProcessingException e) {
throw new RuntimeException("Failed to parse meta JSON string", e);
}
}
}
3. 在字段或类上绑定:
@JsonDeserialize(using = MetaDeserializer.class) private Meta meta;
⚠️ 注意:每次 new 一个 ObjectMapper 开销大,应复用全局单例实例。
性能与线程安全的关键细节
自定义 JsonDeserializer 本身是无状态的,可以安全复用;但里面如果 new ObjectMapper,不仅慢,还可能引发线程安全问题(ObjectMapper 是线程安全的,但频繁构造浪费资源)。
正确做法是注入或持有共享的 ObjectMapper 实例:
- 在 Spring 环境中,直接
@Autowired ObjectMapper objectMapper - 非 Spring 环境,用 static final 或依赖注入方式传入
- 避免在
deserialize()中做 I/O、数据库查询、远程调用等阻塞操作 - 如果嵌套 JSON 字符串可能非法,建议加
try/catch并返回默认值或抛JsonMappingException,而非RuntimeException
最常被忽略的一点:Jackson 默认不会对字符串字段做任何 JSON 解析——它只认字面量。所以当你看到 "meta": "{...}" 被当成普通字符串塞进 String meta 字段时,别怀疑配置,那是设计使然;要改变行为,就必须显式告诉 Jackson:“这个字符串,其实是 JSON,麻烦再解一次”。


















