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

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

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

问题

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

spring:
  jackson:
    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:

spring:
  jackson:
    property-naming-strategy: SNAKE_CASE
    serialization:
      write-date-timestamps-as-nanoseconds: false
      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:

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

...this is a custom (intercepted within a @RestControllerAdvice) response for a 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"
}

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

{
  "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?!
}

> 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:
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 :/
}

答案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:

确定