본문 바로가기

스프링 부트

@JsonProperty, @JsonNaming

2021-04-30글

이 글을 쓴 배경...

클라이언트에서 HTTP Body에 Json을 실어 요청을 실어 보내고 이를 받는 DTO를 만들어 사용하고 있었다.
하지만 API 명세로 클라이언트에서는 key 네이밍으로 스네이크 케이스를 사용하고,
DTO 필드로는 스네이크 케이스를 사용하기 때문에 Jackson이 DTO에 제대로 매핑하지 못해 예외가 발생했다.

그래서 API 명세에서, 클라이언트가 key를 스네이크 케이스를 사용하여 보내주도록 바꾸려 하였으나,
똑똑한 스프링은 이를 위한 어노테이션을 제공해주고 있었다.

문제 상황 테스트

TestDto

public class TestDto {
    private Long testId;

    public TestDto() {
    }

    public TestDto(Long testId) {
        this.testId = testId;
    }

    public Long getTestId() {
        return testId;
    }

    public void setTestId(Long testId) {
        this.testId = testId;
    }
}
  • 필드가 스네이크 케이스

Test2Dto

public class Test2Dto {
    private Long test_id;

    public Test2Dto() {
    }

    public Test2Dto(Long test_id) {
        this.test_id = test_id;
    }

    public Long getTest_id() {
        return test_id;
    }

    public void setTest_id(Long test_id) {
        this.test_id = test_id;
    }
}
  • 필드가 카멜 케이스

TestController

@RestController
public class TestController {
    @GetMapping("/test")
    public ResponseEntity<TestDto> test() {
        return ResponseEntity.ok().body(new TestDto(10L));
    }

    @PostMapping("/test")
    public ResponseEntity<TestDto> test(@RequestBody TestDto testDto) {
        System.out.println(testDto.getTestId());
        return ResponseEntity.ok().body(new TestDto(10L));
    }

    @GetMapping("/test2")
    public ResponseEntity<Test2Dto> test2() {
        return ResponseEntity.ok().body(new Test2Dto(10L));
    }

    @PostMapping("/test2")
    public ResponseEntity<Test2Dto> test2(@RequestBody Test2Dto testDto) {
        System.out.println(testDto.getTest_id());
        return ResponseEntity.ok().body(new Test2Dto(10L));
    }
}

이 때 다음과 같은 테스트를 돌려본다.

@Test
void postTest() throws Exception {
    // given
    Test2Dto testDto = new Test2Dto(10L);
    String json = new ObjectMapper().writeValueAsString(testDto);
    System.out.println(json);

    // then
    mockMvc.perform(MockMvcRequestBuilders
            .post("/test")
            .content(json)
            .header("Content-Type", "application/json"))
            .andDo(print())
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.test_id").value(10L));
}

 

결과는 실패한다!
왜냐하면 클라이언트는 스네이크 케이스로 키 값을 보내고 응답에 대한 키 값도 스네이크 표기법이었는데,
반환하는 DTO의 필드가 카멜 케이스이기 때문에 자동으로 키 값이 카멜 케이스로 잡히기 때문이다.

프론트에서는 스네이크 케이스가 컨벤션이고, 우리 자바에서는 카멜 케이스가 컨벤션인데...
그럼 DTO의 필드를 스네이크로 바꿔야 하나? 혹은 요청의 키 값을 카멜로 바꿔야 하나? 🤷‍♀️


@JsonProperty

만능 스프링은 역시 이에 대한 어노테이션을 제공해준다.
@JsonProperty 는 JSON 변환 시 key 이름을 우리가 원하는 대로 설정할 수 있게 해준다.
이 어노테이션을 사용하기 위해서는 jackson 라이브러리를 사용하여야 하지만, 이미 스프링 내부에서는 jackson을 사용하고 있다.

사용하는 방법은 원하는 필드에 해당 어노테이션을 달고, 매핑할 key 이름을 옵션으로 준다.

public class TestDto {
    @JsonProperty("test_id")
    private Long testId;

    public TestDto() {
    }

    public TestDto(Long testId) {
        this.testId = testId;
    }

    public Long getTestId() {
        return testId;
    }

    public void setTestId(Long testId) {
        this.testId = testId;
    }
}

 

이렇게 되면 테스트 성공이다!

그러면 필드 하나하나에 이렇게 매핑해주어야하나...?


@JsonNaming

다행히도 이런 중복되는 작업을 스프링 어노테이션으로 해결할 수 있다.
클래스에 @JsonNaming 을 붙이면 모든 필드에 대한 표기법 매핑을 정할 수 있다.

import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.annotation.JsonNaming;

@JsonNaming(value = PropertyNamingStrategy.SnakeCaseStrategy.class)
public class TestDto {
    private Long testId;

    public TestDto() {
    }

    public TestDto(Long testId) {
        this.testId = testId;
    }

    public Long getTestId() {
        return testId;
    }

    public void setTestId(Long testId) {
        this.testId = testId;
    }
}

 

참고로 표기법 전략은 SnakeCaseStrategy 말고도 다양하다!

전역적으로 설정하고 싶어!

application.properties 에서 다음과 같이 애플리케이션 설정으로 둘 수도 있다.
이러면 각각의 클래스에 어노테이션을 달아주지 않아도 모든 키 매핑에 대한 처리가 가능하다!

spring.jackson.property-naming-strategy=SNAKE_CASE