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

Spring Boot控制器如何仅记录JSON请求的键(含嵌套对象)且无需解析迭代

解决方案:无需手动迭代即可记录JSON请求的所有键(含嵌套)

嘿,这个需求我刚好在项目里处理过,给你两个简便的实现思路,完全不用自己手动写递归迭代逻辑来解析嵌套对象,利用Spring和Jackson的原生能力就能搞定:

方案一:使用@ControllerAdvice结合Jackson JsonParser(推荐)

这个方案贴合Spring的请求处理流程,不用侵入控制器代码,在请求体转换成Item对象之前就完成键的提取:

1. 编写RequestBodyAdvice类

@ControllerAdvice(basePackages = "com.yourpackage.controller") // 替换成你的控制器所在包
public class RequestKeyLoggingAdvice implements RequestBodyAdvice {
    private static final Logger logger = LoggerFactory.getLogger(RequestKeyLoggingAdvice.class);
    private final ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        // 只拦截目标控制器的addItem方法
        return "addItem".equals(methodParameter.getMethod().getName()) &&
               methodParameter.getContainingClass().isAssignableFrom(YourController.class); // 替换成你的控制器类名
    }

    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
        // 读取请求体字节
        byte[] bodyBytes = StreamUtils.copyToByteArray(inputMessage.getBody());
        String requestBody = new String(bodyBytes, StandardCharsets.UTF_8);

        // 用Jackson的JsonParser自动遍历所有字段名(含嵌套)
        Set<String> allRequestKeys = new LinkedHashSet<>();
        try (JsonParser parser = objectMapper.getFactory().createParser(requestBody)) {
            JsonToken token;
            while ((token = parser.nextToken()) != null) {
                if (token == JsonToken.FIELD_NAME) {
                    allRequestKeys.add(parser.getCurrentName());
                }
            }
        }

        // 记录所有键
        logger.info("Received request keys for addItem: {}", String.join(", ", allRequestKeys));

        // 包装输入流,保证后续流程能正常读取请求体
        return new HttpInputMessage() {
            @Override
            public InputStream getBody() throws IOException {
                return new ByteArrayInputStream(bodyBytes);
            }

            @Override
            public HttpHeaders getHeaders() {
                return inputMessage.getHeaders();
            }
        };
    }

    @Override
    public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return body;
    }

    @Override
    public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return body;
    }
}

方案二:使用拦截器+请求体包装

如果更喜欢用拦截器的方式,也可以通过包装请求体来读取并提取键:

1. 实现请求体包装类(解决请求体只能读一次的问题)

public class CachedBodyHttpServletRequest extends HttpServletRequestWrapper {
    private final byte[] cachedBody;

    public CachedBodyHttpServletRequest(HttpServletRequest request) throws IOException {
        super(request);
        this.cachedBody = StreamUtils.copyToByteArray(request.getInputStream());
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        return new CachedBodyServletInputStream(cachedBody);
    }

    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(new ByteArrayInputStream(cachedBody)));
    }

    public byte[] getCachedBody() {
        return cachedBody;
    }

    private static class CachedBodyServletInputStream extends ServletInputStream {
        private final ByteArrayInputStream inputStream;

        public CachedBodyServletInputStream(byte[] cachedBody) {
            this.inputStream = new ByteArrayInputStream(cachedBody);
        }

        @Override
        public int read() throws IOException {
            return inputStream.read();
        }

        @Override
        public boolean isFinished() {
            return inputStream.available() == 0;
        }

        @Override
        public boolean isReady() {
            return true;
        }

        @Override
        public void setReadListener(ReadListener readListener) {
            throw new UnsupportedOperationException();
        }
    }
}

2. 编写拦截器

@Component
public class RequestKeyLoggingInterceptor implements HandlerInterceptor {
    private static final Logger logger = LoggerFactory.getLogger(RequestKeyLoggingInterceptor.class);
    private final ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 只拦截目标接口
        if ("/v1/myapi".equals(request.getRequestURI()) && HttpMethod.POST.matches(request.getMethod())) {
            CachedBodyHttpServletRequest cachedRequest = new CachedBodyHttpServletRequest(request);
            String requestBody = new String(cachedRequest.getCachedBody(), StandardCharsets.UTF_8);

            Set<String> allRequestKeys = new LinkedHashSet<>();
            try (JsonParser parser = objectMapper.getFactory().createParser(requestBody)) {
                JsonToken token;
                while ((token = parser.nextToken()) != null) {
                    if (token == JsonToken.FIELD_NAME) {
                        allRequestKeys.add(parser.getCurrentName());
                    }
                }
            }

            logger.info("Received request keys: {}", String.join(", ", allRequestKeys));
            // 替换请求为包装后的实例,保证后续流程正常读取
            request = cachedRequest;
        }
        return true;
    }
}

3. 注册拦截器

@Configuration
public class WebConfig implements WebMvcConfigurer {
    private final RequestKeyLoggingInterceptor loggingInterceptor;

    public WebConfig(RequestKeyLoggingInterceptor loggingInterceptor) {
        this.loggingInterceptor = loggingInterceptor;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loggingInterceptor);
    }
}

关键说明

  • 两种方案都利用Jackson的JsonParser自动遍历JSON的所有字段,不管嵌套多少层,都会提取出所有键,完全不用自己写递归迭代逻辑。
  • 使用LinkedHashSet可以保持键的顺序和请求体中的顺序一致。
  • 都处理了请求体只能读取一次的问题,通过包装输入流保证后续流程正常工作。

内容的提问来源于stack exchange,提问作者drunkenfist

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 10:36:42