ABOUT ME

Library + Laboratory

Today
Yesterday
Total
  • Lombok Builder, Jackson을 이용하여 Json data 받아오기 (Get Json data using Lombok Builder, Jackson)
    Library/Backend 2020. 4. 11. 14:48

    안녕하세요.

    이번 시간에는 Spring 기반 Web application에서 흔히 사용하는 Lombok 및 Jackson을 이용해서, Builder 기반 Json Data를 받는 방법에 대해 간략하게 공유하고자 합니다.


    우선 Json data를 받는다는 것은 무엇을 의미할까요? Json data를 받는다는 것은 특정 Data source를 통해 Json으로 된 형태의 Data를 받고, 그 받은 Data를 내 Application에서 정의해놓은 특정 Data class로 인식시키는 것(이를 Deserialize라고 하죠)을 의미합니다. Data source에는 여러가지가 있는데, Web application에서는 흔히 HTTP 기반의 API가 일반적이라고 볼 수 있겠습니다. 그럼 코드를 살펴보도록 하죠.

     

    가장 간단한 경우는 Data source로 부터 받는 Data field name과 Application 내의 Data field name 온전하게 일치하는 경우입니다. (Lombok:1.18.10, Jackson:2.9.7 기준)

    @Getter
    @Builder
    @NoArgsConstructor(access = AccessLevel.PRIVATE)
    @AllArgsConstructor(access = AccessLevel.PRIVATE)
    @JsonIgnoreProperties(ignoreUnknown = true)
    public class AssetDto {
        private String currency;
        private String balance;
        private String locked;
    }

     

    간단하게 @Builder, @NoArgsConstructor, @AllArgsConstructor만 선언해주면 됩니다. field에 별도의 Annotation은 없습니다.

    @JsonIgnoreProperties(ignoreUnknown = true)는 만약 Data source로 부터 Application에서 원치 않는 추가적인 data가 제공되었을 때, Application에서 이를 무시하도록 만드는 설정입니다.

     

    그렇다면 만약 이름이 일치하지 않을 경우엔 어떻게 해야 될까요?

     

    @Getter
    @Builder
    @NoArgsConstructor(access = AccessLevel.PRIVATE)
    @AllArgsConstructor(access = AccessLevel.PRIVATE)
    @JsonIgnoreProperties(ignoreUnknown = true)
    public class AssetDto {
        private String currency;
        private String balance;
        private String locked;
        @JsonProperty("avg_buy_price")
        private String averagePurchasePrice;
        @JsonProperty("avg_buy_price_modified")
        private boolean averagePurchasePriceModified;
    }

    일치하지 않을 때는 이렇게 @JsonProperty를 이용해 Data source에서 제공하는 이름을 지정해줘야 합니다. 그런데 가만보니 Builder가 있기 때문에 Constructor를 이용해 Dto를 만들 필요가 없습니다. 그래서 access level을 명시해서 생성자를 외부에서 호출하는 경우를 차단했는데, Constructor 없이 그냥 Builder만 사용하면 안될까요?

     

    Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: 
    	Cannot construct instance of `...AssetDto` (no Creators, like default construct, exist): 
        	cannot deserialize from Object value (no delegate- or property-based Creator) at [Source: UNKNOWN; line: -1, column: -1]

    네. 안됩니다. 그냥 @Builder를 사용하게 되면 이와 같이 instance 생성을 하지 못하는 문제가 발생합니다.

    ... 그래도 뭔가 아쉽습니다. Constructor를 실제로 사용하지도 않고 신경 쓰고싶지도 않은데, 저렇게 Annotation을 남겨야 한다는 사실이 말이죠.

     

    다행히도 방법은 있습니다.

    @Getter
    @Builder
    @JsonDeserialize(builder = AssetDto.AssetDtoBuilder.class)
    @JsonIgnoreProperties(ignoreUnknown = true)
    public class AssetDto {
        @JsonProperty("currency")
        private String currency;
        @JsonProperty("balance")
        private String balance;
        @JsonProperty("locked")
        private String locked;
        @JsonProperty("avg_buy_price")
        private String averagePurchasePrice;
        @JsonProperty("avg_buy_price_modified")
        private boolean averagePurchasePriceModified;
    }
    @Getter
    @Builder(builderClassName = "AssetDtoBuilder", toBuilder = true)
    @JsonDeserialize(builder = AssetDto.AssetDtoBuilder.class)
    @JsonIgnoreProperties(ignoreUnknown = true)
    public class AssetDto {
        private String currency;
        private String balance;
        private String locked;
        @JsonProperty("avg_buy_price")
        private String averagePurchasePrice;
        @JsonProperty("avg_buy_price_modified")
        private boolean averagePurchasePriceModified;
        
        @JsonPOJOBuilder(withPrefix = "")
        public static class AssetDtoBuilder {
        }
    }

    Constructor annotation 들을 모두 제거하고 @JsonDeserialize, @JsonProperty 또는 @JsonPOJOBuilder를 이용해서 위와 같이 2가지 방법으로 구현해낼 수 있습니다.

    먼저 @JsonDeserialize를 이용해 Deserialize 하는데 어떠한 builder를 쓰는지 명시해줍니다.

    그리고 @JsonProperty를 통해 deserialize 대상 이름을 지정해주거나, @JsonPOJOBuilder를 이용해 Jackson 기본 builder의 mapping method의 "with" prefix를 제거해주면 됩니다. (lombok이 제공하는 builder는 별도의 prefix가 없음)

    두개 중 무엇을 쓸지는 취향의 문제이지만, 데이터 이름이 같은 경우가 적다면 @JsonProperty를 사용하고, 그렇지 않으면 @JsonPOJOBuilder를 쓰는 것이 코드를 더 간결하게 만들어 줄 수 있을 것입니다.

     


     

    마무리 하기 전에, 잠깐 이 글을 작성한 진짜 의도에 대해 이야기를 하겠습니다.

    사실 이 내용은 구글링해보면 잘 나오는 내용입니다. 하지만 구글링해서 붙여넣으면 끝나는게 과연 옳은걸까요?

     

    개인차가 있겠지만 저는 코드는 기본적으로 어떠한 기능에 대해 필요한 코드만 있어야 하며, 그 필요한 코드가 갖고 있는 의미가 충분히 명시적일 때 좋은 코드라고 생각합니다.

    그래서 흔히 Library가 제공해주는 Annotation(@Getter, @Builder ...)을 많이 사용하게 됩니다. 하지만 여기에는 주의해야할 점이 있습니다. 바로, 자신이 사용하는 Annotation이 어떻게 구성되는지 모르고 맹목적으로 사용해선 안된다는 것입니다.

    맹목적으로 사용하다가 발생되는 문제의 대표적인 예로 Annotation이 적용되는 순서에 따라 원치않게 특정 configuration을 덮어 쓰는 문제가 있습니다. 이 문제는 심지어 Compile time때는 잘 감지가 안되고 Run time에서 오작동하는 문제가 되기 때문에 아주 치명적이죠.

    @Data, @Value 등의 다른 Annotation 들을 묶어서 제공하는 Annotation을 경각심 없이 사용하는 것도 역시 여러가지 문제를 유발할 수 있습니다. (나중에 Annotation을 집중적으로 다루는 글을 작성해보도록 하겠습니다. ^^;)

    다시 말해, 불필요한 Annotation 사용을 지양하고 자신이 사용하고 있는 Annotation이 의미하는 바를 명확히 이해하고 사용하는게 중요하다고 생각합니다.

     

    안타깝게도 관련된 자료들을 찾아보는 과정에서, 이러한 관점에서 접근하는 글은 없었습니다.

    단순히 직면한 문제를 해결하는데만 초점이 맞춰져 있습니다. 잠재적인 문제가 될 수 있는 부분들은 생략된 채 말이죠.

    그래서 이번에 이 내용을 위와 같은 관점에서 정리하면서, 특정 문제를 해결하면서 발생할 수 있는 또 다른 문제들을 짚어보고 싶었습니다.

    이 글을 읽는 독자분들도 그냥 "아 이렇게 구현하면 되겠구나." 하고 넘어가는 것이 아닌, "이렇게 구현했을 때 발생할 수 있는 다른 문제는 없을까?"를 한번 쯤은 더 고민해보시면 좋을 것 같습니다.

     

    감사합니다.


    Reference

    https://projectlombok.org/features/Builder

    https://www.thecuriousdev.org/lombok-builder-with-jackson/

    https://stackoverflow.com/a/49124922

     

    댓글