문제 상황
Spring Request DTO의 파라미터로 아래와 같이 gTotal이라는 필드를 만들었다.
import java.math.BigDecimal;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
public class TestDTO{
private DocsPackingGroupTotal gTotal;
@Getter
@Setter
public static class DocsPackingGroupTotal{
private BigDecimal netWeight;
private BigDecimal grossWeight;
}
}
@RestController
@RequiredArgsConstructor
@RequestMapping("/test")
public class TestController {
private final RequestDtoTestService requestDtoTestService;
@PostMapping
public ResponseEntity<Void> test(@RequestBody TestDTO param) throws Exception {
requestDtoTestService.insert(param);
return ResponseEntity.ok().build();
}
}
@Service
@RequiredArgsConstructor
public class RequestDtoTestService {
public void test(TestDTO dto) {
System.out.println(dto.toString());
}
}
아래와 같이 Request를 보내보니 DTO에 null이 들어왔다.
[POST] http://localhost:8080/test
{
"gTotal":{
"netWeight": 123,
"grossWeight": 456
}
}
원인 (Jackson의 Property 명명 규칙)
이유는 Spring에서 JSON과 DTO를 매핑해주는 Jackson 때문이었다.
Jackson은 DTO의 필드명이 아니라 get메서드의 이름을 가지고 Json Key를 만든다.
이때 Jackson이 Json 프로퍼티를 만드는 규칙은 아래와 같다.
- 맨 앞 두 글자가 모두 대문자인 경우 이어진 대문자를 모두 소문자로 변경한다.
- 나머지 모든 케이스에서는 맨 앞 글자만 소문자로 바꿔준다.
Lombok의 @Getter를 사용했기 때문에 gTotal의 get메서드는 아래와 같이 자동으로 만들어진다.
Lombok은 단순히 필드명의 맨 앞 글자만 대문자로 변경하고 get과 set 메서드를 만든다.
public DocsPackingGroupTotal getGTotal() {
return this.gTotal;
}
Jakson은 getGTotal을 가지고 JSON Key를 만드니까 JSON Key는 gTotal이 아니라 gtotal이 된다.
Request의 JSON에서 gTotal에서 gtotal로 변경하면 값이 잘 들어간 것을 확인할 수 있다.
[POST] http://localhost:8080/test
{
"gtotal":{
"netWeight": 123,
"grossWeight": 456
}
}
해결방법
필드명을 변경하거나, @JsonProperty("Json 필드 이름")를 사용하면 된다.
Jakson 매핑을 프로퍼티가 아닌 멤버변수로 할 수 있기 위해 Jakson은 @JsonProperty 어노테이션 API를 제공한다.
@JsonProperty는 Jackson에게 JSON의 JSON Key를 @JsonProperty 어노테이션이 달린 Java 필드 이름에 매핑하도록 지시한다.
아래와 같이 @JsonProperty("Json 필드 이름")으로 어노테이션을 DTO 필드에 붙이면 된다.
@Getter
@Setter
@NoArgsConstructor
public class TestDTO{
@JsonProperty("gTotal") //JSON의 gTotal이 DTO의 gTotal과 매핑된다.
private DocsPackingGroupTotal gTotal;
@Getter
@Setter
public static class DocsPackingGroupTotal{
private BigDecimal netWeight;
private BigDecimal grossWeight;
}
}
[POST] http://localhost:8080/test
{
"gTotal":{
"netWeight": 9999,
"grossWeight": 10000
}
}
여기서 주의할 점은 @JsonProperty("Json 필드 이름")으로 사용해야지, @JsonProeprty만 붙이면 안된다.
@JsonProperty만 붙이면 조회 시 gTotal과 gtotal 두개가 나오게 된다.
왜냐하면 @JsonProperty를 붙여준 gTotal도 getGTotal도 모두 JSON Key로 Jackson이 만들기 때문이다.
아래와 같이 @JsonProperty만 붙이고 요청을 보내보면 gTotal과 gtotal이 모두 응답으로 오는 것을 확인할 수 있다.
@Getter
@Setter
@NoArgsConstructor
public class TestDTO{
@JsonProperty
private DocsPackingGroupTotal gTotal;
@Getter
@Setter
public static class DocsPackingGroupTotal{
private BigDecimal netWeight;
private BigDecimal grossWeight;
}
}
@Service
@RequiredArgsConstructor
public class RequestDtoTestService {
public TestDTO test(TestDTO dto) {
System.out.println(dto.toString());
return dto;
}
}
@RestController
@RequiredArgsConstructor
@RequestMapping("/test")
public class TestController {
private final RequestDtoTestService requestDtoTestService;
@PostMapping
public ResponseEntity<TestDTO> test(@RequestBody TestDTO param) throws Exception {
return ResponseEntity.ok().body(requestDtoTestService.test(param));
}
}
Request
[POST] http://localhost:8080/test
{
"gTotal":{
"netWeight": 9999,
"grossWeight": 10000
}
}
Response
{
"gTotal": {
"netWeight": 9999,
"grossWeight": 10000
},
"gtotal": {
"netWeight": 9999,
"grossWeight": 10000
}
}
@JsonIgnore
참고로, Jackson은 필드명이 아닌 get메서드의 이름을 가지고 JSON Key를 만드므로, 아래와 같이 필드명 없이 get 메서드만 존재해도 ReseponseBody에 나올 수 있다.
@Getter
@Setter
@NoArgsConstructor
public class TestDTO{
@JsonProperty("gTotal")
private DocsPackingGroupTotal gTotal;
@Getter
@Setter
public static class DocsPackingGroupTotal{
private BigDecimal netWeight;
private BigDecimal grossWeight;
}
// get 메서드만 존재
public List<String> getRequiredFields(){
List<String> requiredFields = new ArrayList<>();
requiredFields.add("Required Field 1");
requiredFields.add("Required Field 2");
return requiredFields;
}
}
Request
[POST] http://localhost:8080/test
{
"gTotal":{
"netWeight": 9999,
"grossWeight": 10000
}
}
Response
{
"requiredFields": [
"Required Field 1",
"Required Field 2"
],
"gTotal": {
"netWeight": 9999,
"grossWeight": 10000
}
}
만약 get메서드가 ResponseBody에 나오는 것을 원치 않는다면, get메서드 위에 @JsonIgnore을 붙여주면 된다.
@Getter
@Setter
@NoArgsConstructor
public class TestDTO{
@JsonProperty("gTotal")
private DocsPackingGroupTotal gTotal;
@Getter
@Setter
public static class DocsPackingGroupTotal{
private BigDecimal netWeight;
private BigDecimal grossWeight;
}
//@JsonIgnore을 붙이면 Jackson이 JSON key만들 때 무시한다.
@JsonIgnore
public List<String> getRequiredFields(){
List<String> requiredFields = new ArrayList<>();
requiredFields.add("Required Field 1");
requiredFields.add("Required Field 2");
return requiredFields;
}
}
그럼 아래와 같이 Response에 requiredFields가 포함되지 않는다.
Response
{
"gTotal": {
"netWeight": 9999,
"grossWeight": 10000
}
}
참조
https://bcp0109.tistory.com/309
Spring Request DTO 에 null 값이 들어가는 이유 (Jackson, Lombok)
Overview Spring Boot 로 REST API 를 테스트 하다가 이상한 이슈에 직면했습니다. 클라이언트에서 @RequestBody 로 요청을 받기 위한 DTO 클래스를 만들고 값을 입력 받았는데 null 값이 입력되는 겁니다. 처
bcp0109.tistory.com
'트러블슈팅' 카테고리의 다른 글
Mac에서 올린 파일이 Window에서 다운 받을 때, 파일명의 자음 모음이 분리되는 현상 (0) | 2022.05.02 |
---|---|
Pod들이 143 Error로 계속 리부팅되는 문제 (0) | 2022.04.27 |
AWS EC2 인스턴스 제거 후 비용 청구 문제 (원인: 탄력적 IP) (0) | 2022.04.03 |
Github에 SSH 키 등록 (0) | 2022.03.27 |
VirtualBox에서 E_FAIL (0x80004005) 에러 해결 방법 - Fasoo 프로그램 삭제 (0) | 2022.02.18 |