【问题标题】:Spring Boot default error message(s) is not honoring Jackson's Property Naming StrategySpring Boot 默认错误消息不符合 Jackson 的属性命名策略
【发布时间】:2020-08-13 19:06:40
【问题描述】:

我有一个 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",
}

...这是对ConstraintViolationException 的自定义(在@RestControllerAdvice 中截获)响应:

{
    "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)));

有没有办法告诉框架尊重杰克逊的设置?还是在配置方面我还缺少其他任何东西?


以下可用于重现响应:

  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",
  "status": 404,
  "error": "Not Found",
  "message": null,
  "requestId": "207921a8-2"     <== Again, no snake case here :/
}

【问题讨论】:

  • 当您在控制器建议中创建自定义响应时,Jackson 序列化了什么?我怀疑这可能是由于 Jackson 在使用 JavaBean 样式的属性和 Map 序列化 POJO 时表现不同。 minimal reproducible example 将有助于确定到底发生了什么。
  • 问题不在控制器建议中;该响应很好(阅读:使用蛇盒)。错误的是通过Mono.error(new ResponseStatusException(HttpStatus.NOT_FOUND)) 生成的。无论如何,控制器建议使用模仿DefaultErrorAttributes 的自定义类。您可以在 start.spring.io 中创建一个新的 Spring Boot 2.3.3 WebFlux 项目,使其成为 Gradle / Java 11,然后将 DemoApplication 替换为我在(更新的)问题中的内容。
  • 我知道问题不在于控制器建议,这就是为什么我想知道它是如何实现的,以便我可以将它与 Spring Boot 的错误控制器的工作方式进行比较。

标签: java spring-boot spring-webflux


【解决方案1】:

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

就目前而言,如果您想在映射键被序列化时自定义其格式,您必须配置您的ObjectMapper 以使用自定义StringKeySerializer

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-01-03
    • 2020-10-30
    • 2020-03-18
    • 2020-06-06
    • 2021-07-09
    • 1970-01-01
    相关资源
    最近更新 更多