고딩왕 코범석

Controller에서 다형성을 활용해 JSON 객체를 받아보자! 본문

Language & Framework/Spring

Controller에서 다형성을 활용해 JSON 객체를 받아보자!

고딩왕 코범석 2021. 6. 7. 16:51
반응형

안녕하세요! 이번 포스팅에서는 다형성을 활용하여 JSON 객체를 받아본 내용을 정리하는 포스팅입니다. 버르장머리 있는 말투는 너무 오랜만이라 어색합니다. 항상 가짜뉴스 제보는 환영합니다!


상황

꺼진 JPA도 다시 보기 위해 김영한님의 강의인 JPA 활용편과 비슷하게 모델링해서 진행했습니다. 완전 똑같지는 않고 차이점이 몇가지 있는데 다음과 같습니다.

image

Item과 Album, Book, Movie는 원래 싱글테이블 전략이었으나 조인 전략으로 변경하였고, Category와 Item의 연관 관계가 ManyToMany였으나 OneToMany, ManyToOne으로 바꿔서 진행했습니다.


Album과 Book, Movie를 등록할 때 단순히 컨트롤러에 api를 추가하면 되지만, 저는 한 메서드로 처리하고 싶었습니다. 만약 아이템의 종류가 폭증하게 되면 또 api를 만들어서 반복해야 하는 일이니깐요..

DTO 클래스 구조를 잠깐 보고 가겠습니다.

@Getter @Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor @SuperBuilder
public abstract class ItemDto {

    private String name;
    private int price;
    private int stockQuantity;
    private String categoryId;
    private ItemType itemType;

    public enum ItemType {
        ALBUM, BOOK, MOVIE;
    }

    @Getter @Setter @NoArgsConstructor @AllArgsConstructor @SuperBuilder
    public static class AlbumDto extends ItemDto {

        private String artist;
        private String genre;
    }

    @Getter @Setter @NoArgsConstructor @AllArgsConstructor @SuperBuilder
    public static class BookDto extends ItemDto {

        private String author;
        private String isbn;
    }

    @Getter @Setter @NoArgsConstructor @AllArgsConstructor @SuperBuilder
    public static class MovieDto extends ItemDto {

        private String director;
        private String distributor;
    }
}

ItemDto를 추상클래스로 두고 MovieDto, AlbumDto, BookDto 들이 ItemDto를 상속받아 DTO를 구현하는 방식입니다. 그렇다면 각 아이템을 추가할 때 마다 보내는 JSON의 형식을 봅시다.

{
    "name" : "앨범입니다.1",
    "price" : 10000,
    "stockQuantity" : 10000,
    "itemType" : "ALBUM",
    "categoryId" : "1",
    "artist" : "가수",
    "genre" : "장르"
},
{
    "name" : "책입니다.3",
    "price" : 10000,
    "stockQuantity" : 10000,
    "itemType" : "BOOK",
    "categoryId" : "2",
    "author" : "저자",
    "isbn" : "코드"
},
{
    "name" : "영화입니다.",
    "price" : 10000,
    "stockQuantity" : 10000,
    "itemType" : "MOVIE",
    "categoryId" : "3",
    "director" : "감독",
    "distributor" : "제작지원"
}

예전의 저였다면 컨트롤러에 메서드를 추가했을 겁니다. 물론 실제 책, 앨범, 영화는 다른 성격의 물건이기 때문에 엄연히 말하면 분리하는게 맞을 것 같은데, 예제에 충실하기 위해 이렇게 상황을 가정해보고 하나의 컨트롤러에서 어떻게 처리하는지 궁금했습니다.


나의 해결 방법

JsonTypeInfo와 JsonSubType

다형성을 위한 어노테이션이며, 추상 클래스와 구현 클래스를 연결하는데 주로 사용하며 저의 경우처럼 하나의 추상 클래스를 상속받은 클래스가 여러 가지가 있다면 두 어노테이션을 사용하면 됩니다.

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME,
        include = JsonTypeInfo.As.EXISTING_PROPERTY,
        property = "itemType",
        visible = true)
@JsonSubTypes({
        @JsonSubTypes.Type(name = "ALBUM", value = ItemDto.AlbumDto.class),
        @JsonSubTypes.Type(name = "BOOK", value = ItemDto.BookDto.class),
        @JsonSubTypes.Type(name = "MOVIE", value = ItemDto.MovieDto.class)
})
public abstract class ItemDto { /*중략*/ }

  • use = JsonTypeInfo.Id.NAME


    우선 Id Enum Value는 직렬화 중에 식별자를 어떤 것으로 식별할지 정의하는 value 입니다. 구현 클래스마다 이름이 달라서 저는 NAME으로 설정해주었습니다!


    use 부분은 어노테이션이 위치한 클래스의 유형 및 하위 유형의 인스턴스에 대한 정보를 직렬화, 역직렬화 할때 지정해야하는 프로퍼티입니다.

    정리하면 나는 이 클래스를 직렬화할때 클래스명으로 식별하겠다라는 뜻이겠죠?

  • include = JsonTypeInfo.As.EXISTING_PROPERTY


    As Enum Value는 JsonType.Id.NONE을 제외하고 메타 데이터에 대한 표준 유형 방식을 정의하는 value 입니다. EXISTING_PROPERTY를 말하기에 앞서 PROPERTY의 경우 POJO 방식으로 역직렬화 가능하게끔 해주는 값이며, EXISTING_PROPERTY를 사용할 경우 일반적인 접근(?, 제 생각에는 getter, setter를 의미하는 것 같습니다.)으로 역직렬화 및 직렬화 가능하게 해주는 값인 것 같습니다.


    include는 데이터를 직/역직렬화하는데 사용되는 메커니즘을 정의하는 부분입니다.

    정리하면 이 클래스를 직/역직렬화 할 때, POJO의 getter/setter를 사용해 만들겠다!라는 의미 같습니다.

  • property = "itemType"


    JsonTypeInfo.As.PROPERTY를 위한 속성입니다. DTO를 보면 ItemType Enum을 만들었는데 Album, Book, Movie를 구분하는 용도입니다. 이걸 역직렬화 할 때도 설정해주어야 하는데, property = "itemType"로 타입을 구분짓겠다는 의미입니다.


  • visible = true


    타입을 구분하는 식별자(저의 경우는 itemType이겠죠?)를 역직렬화하겠다는 의미입니다. 이걸 따로 설정하지 않으면 이 ItemType을 역직렬화 하지 않아요!


  • @JsonSubTypes.Type(name = "XXX", value = XXX.class)


    직렬화 가능한 하위 클래스들의 유형을 표시하고, JSON에서 사용되는 논리적 이름을 연결하기 위한 JsonTypeInfo와 함께 사용되는 주석입니다.

    .Type의 경우 클래스 이름 기반으로 하위 유형의 타입을 정의하고, name은 식별하기 위한 이름(JsonTypeInfo의 property), value의 경우 클래스를 명시하는 곳입니다!


이제 제대로 작동하는지 보겠습니다!

디버그 모드로 인텔리제이를 실행해서 request를 타입별로 요청할 때 마다 확인해볼게요!

image

image

image

image

이렇게 앨범, 책, 영화를 요청했는데 아주 잘 작동합니다!

이렇게 해서 이번 포스팅을 마무리하겠습니다. 언제나 틀린 정보 제보 및 댓글은 환영합니다!

반응형