반응형

Jackson에서는 필드명을 직렬화/역직렬화하는 네이밍 전략(PropertyNamingStrategy)을 제공한다.
기본적으로는 카멜 케이스를 제공하고 있으며, 전역적으로 네이밍 전략을 변경하거나 혹은 특정 클래스(DTO)에만 전략을 설정할 수도 있다.
다만, 내가 원하는 '대문자_스네이크_케이스' 전략이 지원 대상이 아니어서 커스텀이 필요한 상황이었다.

  • Camel Case(카멜 케이스) : camelCase
  • Snake Case(스네이크 케이스) : snake_case
  • 내가 원하는 대문자 스네이크 : SNAKE_CASE

 

Jackson이 지원해주는 네이밍 전략은 아래와 같다. PropertyNameingStrategy 클래스에 대한 공식 문서를 살펴보면 자세한 정보를 얻을 수 있다 (일부 네이밍 전략은 SnakeCaseStrategy을 대신 사용한다.)

  • KebabCaseStrategy
  • SnakeCaseStrategy
  • LowerCaseStrategy
  • UpperCamelCaseStrategy
  • LowerCaseWithUnderscoresStrategy → SnakeCaseStrategy
  • PascalCaseStrategy → SnakeCaseStrategy

 

개인적으로 문서 말고 코드를 보는 것을 추천한다. 예를 들어 DOT~Strategy 경우 오픈소스에는 존재하지만 문서에는 존재하지 않는다. 버전 업데이트가 아직 안된 것일 수도 있지만 최신 정보를 확인하기에는 소스를 보는 것이 좋은 것 같다.

소문자 스네이크 전략(SnakeCaseStrategy)은 제공해주면서, 대문자는 제공해주지 않다니ㅠㅠ 설마 싶어서 구글링만 한참 했다.
결론은 지원해주지 않는다는 것.

 

Jackson은 카멜 케이스 필드명은 자동으로 직렬화해주지만, 그 이외의 네이밍 룰의 경우 추가적인 방법이 필요하다.
Jackson이 지원해주지 않는 전략의 경우, 어떤 방법이 있는 지 살펴보겠다.

 

(예제) 외부 API 결과

API Response

{
   "RESULTS":[
      {
         "DISEASE_NAME":"유방암",
         "PROBABILITY":[
            0.017665177583694458,
            0.040371835231781006,
         ]
      },
      {
         "DISEASE_NAME":"뇌졸증",
         "PROBABILITY":[
            0.07009720802307129,
            0.1300843358039856,
         ]
      }
}

보면 대문자_스네이크_네이밍룰로 되어있기 때문에 위와 같이 API Response가 오면 필드를 인식하지 못한다.
Jackson이 다른 네이밍룰도 인식할 수 있도록 세 가지 방법을 소개하겠다.

 

1. @JsonProperty를 통한 필드명 지정

Jackson이 인식하지 못하는 필드명을 명시적으로 지정해준다.
@JsonProperty("API응답컬럼") 이런 식으로 필드명을 간단하게 역직렬화할 수 있다.

하지만 직렬화해야하는 RequestDto가 있거나, 필드의 수가 많을 경우 비효울적이다.

public class ResponseNewPredictionPredDiseaseDto {

    @JsonProperty("RESULTS")
    private List<Results> results;

    public static class Results {
        @JsonProperty("DISEASE_NAME")
        String diseaseName;
        @JsonProperty("PROBABILITY")
        List<Double> probability;
    }
}

 

2. 제공되는 PropertyNamingStrategy 활용

Jackson에서 제공하는 PropertyNamingStrategy를 사용해보자. 카멜케이스, 소문자 스네이크 케이스, 케밥 케이스, 파스칼 케이스 등 기본적은 네이밍 전략을 제공해주고 있다. 때문에 당신이 원하는 네이밍 룰이 포함될 가능성이 크다. 그래서 방법을 안내해보겠다.

API Response로 소문자 스네이크(ex: disease_name)로 필드명이 올 경우,
클래스 위에 @JsonNaming 어노테이션을 추가하고 원하는 네이밍 전략의 클래스명을 입력한다.
그리고 적용되길 원하는 필드에 @JsonProperty 어노테이션을 추가한다.

@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
public class ResponseNewPredictionPredDiseaseDto {

    @JsonProperty
    private List<Results> results;

    public static class Results {
        @JsonProperty
        String diseaseName;
        @JsonProperty
        List<Double> probability;
    }
}

 

3. Custom PropertyNamingStrategy 생성

어쨌든 내가 원하는 네이밍 전략을 Jackson에서 제공해주지 않기 때문에, 결국 PropertyNamingStrategy를 상속받아서 UPPER_SNAKE_CASE 를 직렬화/역직렬화 해주는 네이밍 전략을 만들기로 하였다. 이미 Jackson 내에 유사한 코드가 존재한다. 먼저 코드의 전체적인 흐름이나 공통적인 부분을 간단하게 살펴보고, 본인이 원하는 네이밍룰과 유사한 전략 부분을 참고해서 수정해주면 된다.

진짜 간단하다. 여기서 내가 수정한 것은 몇 줄 되지 않는다.

public class CustomUpperSnakeCaseStrategy extends PropertyNamingStrategy.PropertyNamingStrategyBase {

    public static final PropertyNamingStrategy UPPER_SNAKE_CASE = new CustomUpperSnakeCaseStrategy();

    @Override
    public String translate(String input)
    {
        if (input == null) return input; // garbage in, garbage out
        int length = input.length();
        StringBuilder result = new StringBuilder(length * 2);
        int resultLength = 0;
        boolean wasPrevTranslated = false;
        for (int i = 0; i < length; i++)
        {
            char c = input.charAt(i);
            if (i > 0 || c != '_') // skip first starting underscore
            {
                if (Character.isUpperCase(c))
                {
                    if (!wasPrevTranslated && resultLength > 0 && result.charAt(resultLength - 1) != '_')
                    {
                        result.append('_');
                        resultLength++;
                    }
                    wasPrevTranslated = true;
                }
                else
                {
                    wasPrevTranslated = false;
                }
                result.append(c);
                resultLength++;
            }
        }
        return resultLength > 0 ? result.toString().toUpperCase() : input;
    }
}

 

Custom PropertyNamingStrategy를 적용해보자.

@JsonNaming(CustomUpperSnakeCaseStrategy.class)
public class ResponseNewPredictionPredDiseaseDto {

    @JsonProperty
    private List<Results> results;

    public static class Results {
        @JsonProperty
        String diseaseName;
        @JsonProperty
        List<Double> probability;
    }
}

짝짝짝! 정상적으로 데이터를 받았다!


내가 Custom PropertyNamingStrategy를 만든 이유

Jackson은 Spring 개발자들 사이에서 JSON 작업 표준으로 사용되는 라이브러리이기 때문이다.
사실 객체 → JSON 직렬화, JSON → 객체 역직렬화 라는 컨셉에서 PropertyNamingStrategy 말고도 다양한 방법으로 구현이 가능할 것이다. 심지어 Jackson을 쓰지 않아도 된다.
하지만 Jackson의 PropertyNamingStrategy를 쓴다면 어떤 뉘앙스를 전달해줄 수 있을 것 같았다. 예를 들면,

  1. 이 클래스는 Json 필드명에 대해 커스텀 직렬화/역직렬화 하는 애다.
  1. 이 DTO는 어떤 Json 포맷을 받아들일 때, 커스텀 필드명 네이밍룰을 사용한다.

 

그리고 Jackson 네이밍 전략을 전역적으로 설정할 수도 있지만, DTO 단위로 네이밍룰을 선택적으로 적용했는다.
그 이유는 프로젝트 내에서 사용하는 외부 API마다 네이밍룰이 조금씩 다르고, 기본적으로 camelCase 네이밍 전략이 많이 사용되기 때문이다.

 

혹시 다른 방법을 사용하신 분이 계시면 댓글로 알려주세요! 궁금합니다:)

 


참고코드 : https://github.com/FasterXML/jackson-databind/pull/2241/files

반응형