Spring Boot默认错误消息不符合Jackson的属性命名策略

问题描述

我有一个Spring Boot 2.3.2.RELEASE WebFlux应用程序。在application.yml中,我具有以下设置:

spring:
  jackson:
    property-naming-strategy: SNAKE_CASE
    serialization:
      write-date-timestamps-as-nanoseconds: false
      write-dates-as-timestamps: true

实际上,几乎所有JSON响应均以 snake case 格式正确发送回用户代理,但认格式除外。我的意思是框架产生的任何响应。

这是通常的GET

{
  "port_code": "blah","firm_code": "foo","type": "THE_TYPE","status": "BAR",}

...这是针对@RestControllerAdvice自定义(在ConstraintViolationException中被拦截)响应:

{
    "timestamp": 1597344667156,"path": "/path/to/resources/null","status": 400,"error": "Bad Request","message": [
        {
            "field": "id","code": "field.id.Range","message": "Identifier must be a number within the expected range"
        }
    ],"request_id": "10c4978f-3"
}

...最后,这就是Spring Boot从控制器生成HTTP 404的方式:

{
  "timestamp": 1597344662823,"path": "/path/to/resources/312297273","status": 404,"error": "Not Found","message": null,"requestId": "10c4978f-2"     <== NOTICE HERE requestId INSTEAD OF request_id ...what the hell?!
}

这是我在控制器中触发它的方式:return service.findById(id).switchIfEmpty(Mono.error(new ResponseStatusException(HttpStatus.NOT_FOUND)));

有没有办法告诉框架遵守Jackson的设置?还是我在配置方面还缺少什么?


以下内容可用于重现响应:

  1. 创建一个新的Spring Boot 2.3.3 WebFlux项目(使用start.spring.io
  2. 使用Gradle / Java 11.x
  3. 使用application.properties更新spring.jackson.property-naming-strategy = SNAKE_CASE
  4. DemoApplication替换为以下内容
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;
import reactor.core.publisher.Mono;

@SpringBootApplication
public class DemoApplication {
  public static void main(String[] args) {
    SpringApplication.run(DemoApplication.class,args);
  }

  @RestController
  @RequestMapping("/hello")
  public class Ctrl {
    @GetMapping(value = "/{id}",produces = MediaType.APPLICATION_JSON_VALUE)
    public Mono<DummyResponse> get(@PathVariable("id") final String id) {
      if ("ok".equalsIgnoreCase(id)) {
        return Mono.just(new DummyResponse(id));
      }
      return Mono.error(new ResponseStatusException(HttpStatus.NOT_FOUND));
    }

    final class DummyResponse {
      public final String gotIt;

      DummyResponse(final String gotIt) {
        this.gotIt = gotIt;
      }
    }
  }
}
[x80486@archbook:~]$ curl -H "accept: application/json" -X GET http://localhost:8080/hello/ok | jq 
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    15  100    15    0     0    154      0 --:--:-- --:--:-- --:--:--   156
{
  "got_it": "ok"
}
[x80486@archbook:~]$ curl -H "accept: application/json" -X GET http://localhost:8080/hello/notok | jq 
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   140  100   140    0     0   5600      0 --:--:-- --:--:-- --:--:--  5600
{
  "timestamp": "2020-08-14T12:32:49.096+00:00","path": "/hello/notok","requestId": "207921a8-2"     <== Again,no snake case here :/
}

解决方法

Spring Boot的错误控制器为Jackson提供了Map来进行序列化,而您的控制器建议则是对Java对象进行序列化。序列化Java对象时,Jackson会根据JavaBean样式的属性对其进行序列化,并使用属性命名策略来决定每个属性在JSON中的显示方式。序列化地图时,键不会被视为属性,因此属性命名策略无效,Jackson会按原样序列化键。

就目前情况而言,如果要自定义映射键的序列化格式,则必须将ObjectMapper配置为使用自定义StringKeySerializer