You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Jackson的DeserializationProblemHandler中获取完整根JSON字符串

可行实现方案

下面提供3种不同侵入程度的实现方式,可根据业务场景选择:


方案1:通用无侵入(推荐):使用InputDecorator缓存全量输入

通过Jackson自带的InputDecorator在解析前统一缓存全量JSON内容,不需要修改上层调用逻辑,适配所有输入类型(字符串/字节数组/输入流/Reader):

  1. 首先实现自定义输入装饰器,在输入流读取时缓存内容:
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);
    }
}
  1. 在自定义Module中注册这个装饰器:
@Override
public void setupModule(Module.SetupContext context) {
    context.addDeserializationProblemHandler(new UnknownPropertyDeserializationProblemHandler(logger, MESSAGE));
    // 注册输入装饰器
    context.getOwner().setInputDecorator(new CachingInputDecorator());
}
  1. 在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.26 20:36:05