springboot入参下划线转驼峰出参驼峰转下划线

发布时间 2023-12-20 13:32:02作者: Scott_pb

springboot入参出参下划线转驼峰

前言

因为历史原因前端入参和出参都为下划线,下划线对有亿点强迫症的我来说是不可接受的。因此就有了下面这篇。

本篇基于之前的一篇springboot封装统一返回 - Scott_pb - 博客园 (cnblogs.com)

引入xml

因为是基于jackson而spring-boot-starter-web已经包含

<dependency>
	<groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>

工具类

public class JsonUtils {

    private static final ObjectMapper objectMapper = new ObjectMapper();


    /**
     * json字符串转驼峰
     *
     * @param json String
     * @return String
     */
    public static String convertToCamelCaseJson(String json) throws JsonProcessingException {
        JsonNode jsonNode = objectMapper.readTree(json);

        jsonNode = convertKeysToCamelCase(jsonNode);

        return objectMapper.writeValueAsString(jsonNode);
    }


    /**
     * JsonNode的key转驼峰
     *
     * @param jsonNode JsonNode
     * @return JsonNode
     */
    public static JsonNode convertKeysToCamelCase(JsonNode jsonNode) {
        if (jsonNode.isObject()) {
            ObjectNode objectNode = objectMapper.createObjectNode();
            jsonNode.fields().forEachRemaining(entry -> {
                objectNode.set(snakeToCamel(entry.getKey()), convertKeysToCamelCase(entry.getValue()));
            });
            return objectNode;
        }

        if (jsonNode.isArray()) {
            ArrayNode arrayNode = objectMapper.createArrayNode();
            jsonNode.elements().forEachRemaining(entry -> {
                arrayNode.add(convertKeysToCamelCase(entry));
            });

            return arrayNode;
        }

        return jsonNode;
    }

    public static JsonNode convertKeysToSnake(JsonNode jsonNode) {
        if (jsonNode.isObject()) {
            ObjectNode objectNode = objectMapper.createObjectNode();
            jsonNode.fields().forEachRemaining(entry -> {
                objectNode.set(camelToSnake(entry.getKey()), convertKeysToCamelCase(entry.getValue()));
            });
            return objectNode;
        }

        if (jsonNode.isArray()) {
            ArrayNode arrayNode = objectMapper.createArrayNode();
            jsonNode.elements().forEachRemaining(entry -> {
                arrayNode.add(convertKeysToSnake(entry));
            });

            return arrayNode;
        }

        return jsonNode;
    }


    /**
     * 下划线转驼峰
     *
     * @param key String
     * @return String
     */
    public static String snakeToCamel(String key) {
        if (key.indexOf("_") != 0) {
            String[] s = key.split("_");
            StringBuilder stringBuilder = new StringBuilder(s[0]);
            for (int i = 1; i < s.length; i++) {
                stringBuilder.append(s[i].substring(0, 1).toUpperCase()).
                        append(s[i].substring(1));
            }
            return stringBuilder.toString();
        }

        return key;
    }


    /**
     * 驼峰转下划线
     * @param key String
     * @return String
     */
    public static String camelToSnake(String key) {
        int length = key.length();
        StringBuilder sb = new StringBuilder(length);
        for (int i = 0; i < length; i++) {
            char current = key.charAt(i);
            if (Character.isUpperCase(current)) {
                if (i > 0) {
                    sb.append('_');
                }
                sb.append(Character.toLowerCase(current));
                continue;
            }
            sb.append(current);
        }
        return sb.toString();
    }


}

ServletRequest定义

因为requestBody是流形式,所以转驼峰后,需要将转后的数据封装为流到requestBody中,我采用继承HttpServletRequestWrapper的方法重写HttpServletRequest

public class CamelKeyRequestWrapper extends HttpServletRequestWrapper {
  	//修改后的字节数组
    private final byte[] modifiedContent;

  	//json的key转为驼峰
    public CamelKeyRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        this.modifiedContent = camelCaseJson(request);
    }

  	//调用getInputStream时,返回转化后的驼峰json数据
    public ServletInputStream getInputStream() {
        return new ModifiedServletInputStream(new ByteArrayInputStream(modifiedContent));
    }

    @Override
    public BufferedReader getReader() {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }


    /**
     * 下划线转驼峰
     * @param request HttpServletRequest
     * @return byte[]
     * @throws IOException e
     */
    private byte[] camelCaseJson(HttpServletRequest request) throws IOException {
        //没有requestBody
        if (request.getContentType() != null && !request.getContentType().contains("application/json")) {
            return null;
        }

        ServletInputStream inputStream = null;
        BufferedReader reader = null;
        String originalJsonBody, camelCaseJson = null;
        ContentCachingRequestWrapper requestWrapper;
        try {
            requestWrapper = new ContentCachingRequestWrapper(request);
          	//原来的InputStream流,因为是流,所以只能使用一次
            inputStream = requestWrapper.getInputStream();
          	//从流中按照UTF-8格式读取
            reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
          	//定义StringBuilder
            StringBuilder stringBuilder = new StringBuilder();
            String line;
          	//按行读取
            while ((line = reader.readLine()) != null) {
                stringBuilder.append(line);
            }
          	//得到下划线的json字符串
            originalJsonBody = stringBuilder.toString();
          	//将下划线的json字符串转为驼峰的json字符串
            camelCaseJson = JsonUtils.convertToCamelCaseJson(originalJsonBody);


        } catch (Exception e) {
            e.printStackTrace();
        } finally {
          	//不要忘了关闭链接
            assert inputStream != null;
            inputStream.close();
            assert reader != null;
            reader.close();
        }

        assert camelCaseJson != null;
        return camelCaseJson.getBytes();
    }
}

filter拦截器

@Component
@Order(1)
public class RequestBodyFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

      	//判断有requestbody传入
        if (servletRequest.getContentType() == null || !servletRequest.getContentType().contains("application/json")) {
            filterChain.doFilter(servletRequest,servletResponse);
            return;
        }

        //下划线转驼峰requestBody
        filterChain.doFilter(new CamelKeyRequestWrapper((HttpServletRequest) servletRequest),servletResponse);

    }

}

修改ResponseResultHandler

修改上一篇中ResponseResultHandler

@ControllerAdvice
public class ResponseResultHandler<T> implements ResponseBodyAdvice<Object> {
    public static final String RESPONSE_RESULT_ANN = "RESPONSE-RESULT-ANN";

    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (sra == null) {
            return false;
        }

        HttpServletRequest sraRequest = sra.getRequest();
        ResponseResult responseResult = (ResponseResult) sraRequest.getAttribute(RESPONSE_RESULT_ANN);
        return responseResult != null;
    }

    @SneakyThrows
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
                                  Class<? extends HttpMessageConverter<?>> selectedConverterType,
                                  ServerHttpRequest request, ServerHttpResponse response) {
        
      
        if (body instanceof Response) {
            return body;
        }
      
        //List数组
        if (body instanceof List) {
            body = converSnakeList((List<T>) body);

        }

        //PageInfoVo
        if (body instanceof PageInfoVo) {
            List<? extends T> snakeList = converSnakeList((List<T>) ((PageInfoVo<?>) body).getData());
            ((PageInfoVo<T>) body).setData(snakeList);
        }

        //...其他类型...

         if (body instanceof String) {
            return Response.success(body);
         }
        
         return Response.success(body);
    }

    /**
     * 数组的key转为下划线
     * @param data List
     * @return List<? extends T>
     * @throws JsonProcessingException JsonProcessingException
     */
    private List<? extends T> converSnakeList(List<T> data) throws JsonProcessingException {
        int size = data.size();

        //list中有数据
        if (size > 0) {
            ObjectMapper objectMapper = new ObjectMapper();
            List<JsonNode> snakeList = new ArrayList<>(size);

            for (T datum : data) {
                //object转JsonNode
                JsonNode jsonNode = objectMapper.readTree(objectMapper.writeValueAsString(datum));
                //JsonNode的key转为蛇形
                JsonNode snakeJsonNode = JsonUtils.convertKeysToSnake(jsonNode);

                snakeList.add(snakeJsonNode);
            }

            return (List<? extends T>) snakeList;
        }
        return null;
    }
}