问题描述
我有一个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的设置?还是我在配置方面还缺少什么?
以下内容可用于重现响应:
- 创建一个新的Spring Boot
2.3.3
WebFlux项目(使用start.spring.io
) - 使用Gradle / Java
11.x
- 使用
application.properties
更新spring.jackson.property-naming-strategy = SNAKE_CASE
- 将
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
。