[Spring Boot] Jackson Annotation을 사용해보자! @JsonTypeInfo, @JsonSubTypes

froajnzd·2024년 5월 1일
0

hh

목록 보기
1/2
post-thumbnail

도입 배경

polymorphic type으로 request를 받는 DTO를 어떻게 serialize/deserialize 할 것인가를 찾다가 ChatGPT가 @JsonTypeInfo를 쓰는 것을 보고 이게 뭐지?로부터 시작되었다

우리 시스템을 간단하게 설명해보자면,, 그림판으로 그려봤는데 이런식으로 dto를 주고받고 그럿더랫다..

GET은 무난히 Serialize했는데, POST는 request로부터 어떻게 딱 맞는 Type{N}DTO로 Deserialize하지?

고민 끝에 @JsonTypeInfo로 Deserialize할 수 있었다!!

어노테이션

여기서 사용할 어노테이션은 세 개로 간추려 볼 수 있다.

@JsonTypeInfo – 무슨 타입 정보를 serialization에 포함할 것인지 나타낸다

  • Id: use : 클래스/하위 클래스 인스턴스에 대한 타입 정보를 직렬화(serialize)할 때 사용할 타입 메타데이터의 종류, 역직렬화(deserialize) 시 예상되는 내용을 지정
    • NAME : name을 사용하겠다.
    • CUSTOM : 내맘대로 커스텀하겠다.
    • CLASS : class를 사용하겠다.
    • NONE : 아무것도 사용하지 않겠다.
    • DEDUCTION : 너가 알아서 안에 내용보고 추론해라 (서브타입별로 타입, 개수가 다른 경우에 한해서)
    • MINIMAL_CLASS
  • As: include - default값은 JsonTypeInfo.As.PROPERTY
    • PROPERTY : 필드를 새롭게 생성하겠다.
    • EXISTING_PROPERTY : 이미 기존에 있는(내가 만든) 필드를 사용하겠다.
    • EXTERNAL_PROPERTY : 외부의 필드를 사용하겠다.
    • WRAPPER_ARRAY
    • WRAPPER_OBJECT
  • String: property - default값은 ""
    • 필드(property)의 이름을 작성한다.
    • property="type"으로 작성했다고 하자. include=PROPERTY를 했다면 type이라는 필드가 새롭게 생길 것이고, include=EXISTING_PROPERTY를 했다면 기존의 type이라는 필드를 사용할 것이다.
  • Class<?> defaultImpl - default값은 JsonTypeInfo.class
  • Boolean: visible - default값은 false
    • 유형 식별자 값을 JSON 스트림의 일부로 역직렬화기에 전달할지(true), 아니면 TypeDeserializer에서 처리하여 제거할지(false) 정의하는 속성. (직렬화에 영향 X)
    • 기본값인 false는 Jackson이 JsonDeserializer로 전달된 JSON 콘텐츠에서 유형 식별자를 처리하고 제거한다는 의미

@JsonSubTypes – Annotation을 붙인 Sub Type들을 나타낸다. 서브클래스들을 등록한다고 생각해도 좋다.

  • @JsonTypeInfo 바로 아래, 사용할 서브 타입들을 나열한다. value(클래스)와 name을 명시해준다.(name을 사용하는 경우에)

@JsonTypeName – Annotation 클래스에 사용할 타입 이름을 붙인다.

  • 자식 클래스에 어노테이션을 붙인다.

사용 예시

동물원에 있는 동물들 종에 대한 클래스를 만들고, 그 동물들이 상속하는 '동물' class를 만들고자한다.

Animal 클래스에는 @JsonTypeInfo로 Animal을 상속할 동물들을 어떻게 사용할 건지 정의해주었고, @JsonSubTypes로 서브 타입들을 명시해주었다.

Animal 클래스를 상속하는 Dog, Cat 클래스에는 @JsonTypeName으로 property type을 구별할 이름 값을 지정해준다.

public class Zoo {
    public Animal animal;

    @JsonTypeInfo(
      use = JsonTypeInfo.Id.NAME, 
      include = As.PROPERTY, 
      property = "type")
    @JsonSubTypes({
        @JsonSubTypes.Type(value = Dog.class, name = "dog"),
        @JsonSubTypes.Type(value = Cat.class, name = "cat")
    })
    public static class Animal {
        public String name;
    }

    @JsonTypeName("dog")
    public static class Dog extends Animal {
        public double barkVolume;
    }

    @JsonTypeName("cat")
    public static class Cat extends Animal {
        boolean likesCream;
        public int lives;
    }
}

사용해보기: Serialize의 경우

동물원에 Dog 인스턴스를 하나 생성하고자 한다.

@Test
public void whenSerializingPolymorphic_thenCorrect()
  throws JsonProcessingException {
    Zoo.Dog dog = new Zoo.Dog("lacy");
    Zoo zoo = new Zoo(dog);

    String result = new ObjectMapper()
      .writeValueAsString(zoo);

    assertThat(result, containsString("type"));
    assertThat(result, containsString("dog"));
}

아래는 결과! type이 dog으로 Dog 인스턴스가 생긴 것을 알 수 있다.

{
    "animal": {
        "type": "dog",
        "name": "lacy",
        "barkVolume": 0
    }
}

사용해보기: Deserialize의 경우

이젠 json을 input으로 넣겠다.

{
    "animal":{
        "name":"lacy",
        "type":"cat"
    }
}

결과! type이 cat임을 알고, Cat 인스턴스를 생성하였다.

@Test
public void whenDeserializingPolymorphic_thenCorrect()
throws IOException {
    String json = "{\"animal\":{\"name\":\"lacy\",\"type\":\"cat\"}}";

    Zoo zoo = new ObjectMapper()
      .readerFor(Zoo.class)
      .readValue(json);

    assertEquals("lacy", zoo.animal.name);
    assertEquals(Zoo.Cat.class, zoo.animal.getClass());
}

문제 상황과 해결

1. property만 null로 매핑됨

-> visible을 true로 바꿔준다. 기본값이 false였기에, TypeDeserializer에서 제거하기 때문에 null이 되어버린다.
stackoverflow 동일 문제

참고 링크:
https://www.baeldung.com/jackson-annotations#jackson-polymorphic-type-handling-annotations

https://fasterxml.github.io/jackson-annotations/javadoc/2.4/com/fasterxml/jackson/annotation/JsonTypeInfo.html

더 알고 싶다면..
https://www.baeldung.com/jackson-advanced-annotations#bd-overview

profile
Hi I'm 열쯔엉

0개의 댓글