Spring Boot默认错误消息未遵循Jackson的属性命名策略。

huangapple go评论122阅读模式
英文:

Spring Boot default error message(s) is not honoring Jackson's Property Naming Strategy

问题

你的Spring Boot应用中,要让框架遵循Jackson的设置,以使用Snake Case命名策略,你可以尝试在application.yml中添加以下配置:

  1. spring:
  2. jackson:
  3. property-naming-strategy: SNAKE_CASE

这应该会使大多数JSON响应以Snake Case格式返回给用户代理,包括框架生成的响应。如果这个设置没有生效,你可能需要检查是否有其他配置或拦截器干扰了Jackson的行为。

英文:

I have a Spring Boot 2.3.2.RELEASE WebFlux application. In the application.yml I have these settings:

  1. spring:
  2. jackson:
  3. property-naming-strategy: SNAKE_CASE
  4. serialization:
  5. write-date-timestamps-as-nanoseconds: false
  6. write-dates-as-timestamps: true

Indeed, almost any JSON response is sent back to the user-agents correctly: in snake case format, except for the default ones. What I meant by this, is any response generated by the framework.

This is a usual GET:

  1. {
  2. "port_code": "blah",
  3. "firm_code": "foo",
  4. "type": "THE_TYPE",
  5. "status": "BAR",
  6. }

...this is a custom (intercepted within a @RestControllerAdvice) response for a ConstraintViolationException:

  1. {
  2. "timestamp": 1597344667156,
  3. "path": "/path/to/resources/null",
  4. "status": 400,
  5. "error": "Bad Request",
  6. "message": [
  7. {
  8. "field": "id",
  9. "code": "field.id.Range",
  10. "message": "Identifier must be a number within the expected range"
  11. }
  12. ],
  13. "request_id": "10c4978f-3"
  14. }

...and finally this is how Spring Boot generates an HTTP 404 from the controller:

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

> This is how I'm triggering that in the controller: return service.findById(id).switchIfEmpty(Mono.error(new ResponseStatusException(HttpStatus.NOT_FOUND)));

Is there any way to tell the framework to honor Jackson's settings? Or is there anything else I'm missing configuration-wise?


The following can be used to reproduce the responses:

  1. Create a new Spring Boot 2.3.3 WebFlux project (using start.spring.io)
  2. Make it Gradle / Java 11.x
  3. Update application.properties with spring.jackson.property-naming-strategy = SNAKE_CASE
  4. Replace DemoApplication with the following:
  1. import org.springframework.boot.SpringApplication;
  2. import org.springframework.boot.autoconfigure.SpringBootApplication;
  3. import org.springframework.http.HttpStatus;
  4. import org.springframework.http.MediaType;
  5. import org.springframework.web.bind.annotation.GetMapping;
  6. import org.springframework.web.bind.annotation.PathVariable;
  7. import org.springframework.web.bind.annotation.RequestMapping;
  8. import org.springframework.web.bind.annotation.RestController;
  9. import org.springframework.web.server.ResponseStatusException;
  10. import reactor.core.publisher.Mono;
  11. @SpringBootApplication
  12. public class DemoApplication {
  13. public static void main(String[] args) {
  14. SpringApplication.run(DemoApplication.class, args);
  15. }
  16. @RestController
  17. @RequestMapping("/hello")
  18. public class Ctrl {
  19. @GetMapping(value = "/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
  20. public Mono<DummyResponse> get(@PathVariable("id") final String id) {
  21. if ("ok".equalsIgnoreCase(id)) {
  22. return Mono.just(new DummyResponse(id));
  23. }
  24. return Mono.error(new ResponseStatusException(HttpStatus.NOT_FOUND));
  25. }
  26. final class DummyResponse {
  27. public final String gotIt;
  28. DummyResponse(final String gotIt) {
  29. this.gotIt = gotIt;
  30. }
  31. }
  32. }
  33. }
  1. [x80486@archbook:~]$ curl -H "accept: application/json" -X GET http://localhost:8080/hello/ok | jq
  2. % Total % Received % Xferd Average Speed Time Time Time Current
  3. Dload Upload Total Spent Left Speed
  4. 100 15 100 15 0 0 154 0 --:--:-- --:--:-- --:--:-- 156
  5. {
  6. "got_it": "ok"
  7. }
  8. [x80486@archbook:~]$ curl -H "accept: application/json" -X GET http://localhost:8080/hello/notok | jq
  9. % Total % Received % Xferd Average Speed Time Time Time Current
  10. Dload Upload Total Spent Left Speed
  11. 100 140 100 140 0 0 5600 0 --:--:-- --:--:-- --:--:-- 5600
  12. {
  13. "timestamp": "2020-08-14T12:32:49.096+00:00",
  14. "path": "/hello/notok",
  15. "status": 404,
  16. "error": "Not Found",
  17. "message": null,
  18. "requestId": "207921a8-2" <== Again, no snake case here :/
  19. }

答案1

得分: 2

Spring Boot的错误控制器将一个Map交给Jackson进行序列化,而您的控制器建议(serializing a Java object)则将一个Java对象进行序列化。在序列化Java对象时,Jackson会根据JavaBean风格的属性进行序列化,并使用属性命名策略来决定JSON中的每个属性应该如何呈现。在序列化Map时,键(key)不被视为属性(properties),因此属性命名策略不起作用,Jackson会按原样序列化键(key)。

目前来看,如果您想自定义在序列化时映射键的格式,您将需要配置您的ObjectMapper以使用自定义的StringKeySerializer

英文:

Spring Boot's error controller gives Jackson a Map to serialize whereas your controller advice is serialising a Java object. When serializing a Java object, Jackson serializes it based on JavaBean-style properties and uses the property naming strategy to decide how each of those properties should appear in the JSON. When serialising a map, the keys aren't treated as properties so the property naming strategy has no effect and Jackson serialises the keys as-is.

As things stand, if you want to customise the format of map keys as they're serialized you'll have to configure your ObjectMapper to use a custom StringKeySerializer.

huangapple
  • 本文由 发表于 2020年8月14日 03:06:40
  • 转载请务必保留本文链接:https://go.coder-hub.com/63401714.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定