如何在Jackson的DeserializationProblemHandler中获取完整根JSON字符串
可行实现方案
下面提供3种不同侵入程度的实现方式,可根据业务场景选择:
方案1:通用无侵入(推荐):使用InputDecorator缓存全量输入
通过Jackson自带的InputDecorator在解析前统一缓存全量JSON内容,不需要修改上层调用逻辑,适配所有输入类型(字符串/字节数组/输入流/Reader):
- 首先实现自定义输入装饰器,在输入流读取时缓存内容:
import com.fasterxml.jackson.core.IOContext; import com.fasterxml.jackson.core.InputDecorator; import com.google.common.io.ByteStreams; // 无Guava可自行实现流转字节数组逻辑 import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.nio.charset.StandardCharsets; public class CachingInputDecorator extends InputDecorator { @Override public InputStream decorate(IOContext ctxt, InputStream in) throws IOException { // 读取全量字节缓存 byte[] contentBytes = ByteStreams.toByteArray(in); // 缓存JSON字符串到上下文属性 String fullJson = new String(contentBytes, ctxt.getEncoding() == null ? StandardCharsets.UTF_8 : ctxt.getEncoding()); ctxt.setAttribute("full_json_content", fullJson); // 构造新的输入流供后续反序列化使用 return new ByteArrayInputStream(contentBytes); } @Override public Reader decorate(IOContext ctxt, Reader r) throws IOException { // 同理实现Reader的缓存逻辑 String fullJson = com.google.common.io.CharStreams.toString(r); ctxt.setAttribute("full_json_content", fullJson); return new java.io.StringReader(fullJson); } }
- 在自定义Module中注册这个装饰器:
@Override public void setupModule(Module.SetupContext context) { context.addDeserializationProblemHandler(new UnknownPropertyDeserializationProblemHandler(logger, MESSAGE)); // 注册输入装饰器 context.getOwner().setInputDecorator(new CachingInputDecorator()); }
- 在
handleUnknownProperty中直接获取缓存的JSON:
@Override public boolean handleUnknownProperty(final DeserializationContext context, final JsonParser parser, final JsonDeserializer<?> deserializer, final Object beanOrClass, final String propertyName) throws IOException { final Class<?> type = beanOrClass.getClass(); String jsonPath = parser.getParsingContext().pathAsPointer().toString(); // 直接从上下文取提前缓存的完整JSON String json = (String) context.getAttribute("full_json_content"); logger.warn(format, jsonPath, type, json); return false; }
方案2:轻量改造:封装ObjectMapper调用缓存输入
如果不想引入额外的装饰器,也可以封装统一的反序列化工具方法,提前缓存输入内容:
public class JsonUtils { private static final ObjectMapper MAPPER = new ObjectMapper() .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) .registerModule(new UnknownPropertyModule(logger)); public static <T> T readValue(String json, Class<T> clazz) throws JsonProcessingException { try { // 把JSON存入上下文 MAPPER.getDeserializationContext().setAttribute("full_json_content", json); return MAPPER.readValue(json, clazz); } finally { // 清理避免内存泄漏 MAPPER.getDeserializationContext().removeAttribute("full_json_content"); } } // 同理封装字节数组、输入流等重载方法,先把输入转成字符串缓存 }
后续所有反序列化都走这个工具类,Handler中获取缓存的逻辑和方案1一致。
方案3:无前置改造:直接从Parser读取当前节点
如果不想修改任何前置逻辑,也可以在遇到未知属性时直接从Parser读取完整JSON树,转成字符串后再重置解析位置(会有轻微性能损耗,不适合超高QPS场景):
@Override public boolean handleUnknownProperty(final DeserializationContext context, final JsonParser parser, final JsonDeserializer<?> deserializer, final Object beanOrClass, final String propertyName) throws IOException { final Class<?> type = beanOrClass.getClass(); String jsonPath = parser.getParsingContext().pathAsPointer().toString(); // 读取完整JSON树 JsonNode rootNode = parser.readValueAsTree(); String json = rootNode.toString(); logger.warn(format, jsonPath, type, json); // 重置解析器位置,不影响后续反序列化 JsonParser newParser = rootNode.traverse(parser.getCodec()); newParser.nextToken(); // 替换上下文的解析器 context.setParser(newParser); return false; }
内容的提问来源于stack exchange,提问作者tftd
相关产品推荐
相关产品推荐

