개요
OOP와 ORM에서 다형성은 코드의 재사용성과 유지 보수성을 극대화 하는 아름다운 매커니즘이다. 그런데 이것을 ORM과 접목시켰을 때, JSON이 기본적으로 다형성을 제공하지 않기 때문에 상위 클래스나 인터페이스를 통한 다양한 하위 클래스의 객체를 직렬화 <-> 역직렬화 하는 것이 까다로울 수 있다. 이를 해결하기 위해 Jackson과 같은 라이브러리에서는 @JsonSubTypes와 같은 애노테이션을 제공하여 다형적 구조를 효과적으로 관리할 수 있게 도와준다.
이번 글에서는 Jackson의 @JsonSubTypes 애노테이션을 중심으로, 다형성 JSON 데이터를 올바르게 직렬화하고 역직렬화하는 방법에 대해 알아보자.
내용
@JsonSubTypes의 기본 개념
@JsonSubTypes는 Jackson 라이브러리에서 다형성(JSON Polymorphism)을 지원하기 위해 사용하는 애노테이션이다. 이 애노테이션은 기본 클래스나 인터페이스가 여러 하위 클래스 또는 구현 클래스를 가질 때, Jackson에게 어떤 하위 클래스들이 존재하는지 알려주는 역할을 한다. 이를 통해 Jackson은 JSON 데이터를 올바른 구체 클래스 인스턴스로 역직렬화할 수 있다.
@JsonSubTypes의 동작 방식
다형성을 지원하기 위해 Jackson은 @JsonTypeInfo와 함께 @JsonSubTypes를 사용한다. @JsonTypeInfo는 JSON 데이터에 객체의 타입 정보를 포함시키는 방법을 정의하며, @JsonSubTypes는 그 타입 정보에 따라 매핑될 구체적인 클래스들을 지정한다.
- use: 타입 정보를 식별하는 방법을 지정한다. 주로 JsonTypeInfo.Id.NAME을 사용하여 이름 기반으로 타입을 식별한다.
- property: JSON 데이터에서 타입 정보를 나타내는 속성의 이름을 지정한다.
- subtypes: 하위 클래스들을 정의하며, 각 하위 클래스의 타입 이름과 클래스 타입을 매핑한다.
@JsonSubTypes와 @JsonTypeName의 관계
@JsonTypeName은 특정 하위 클래스가 다형성 구조 내에서 사용될 때, 해당 클래스의 타입 이름을 명시적으로 지정하는 데 사용된다. 이 이름은 @JsonSubTypes에서 정의한 name과 일치해야 한다. 이를 통해 Jackson은 JSON 데이터 내의 타입 이름을 기반으로 올바른 클래스로 객체를 매핑할 수 있다.
@JsonSubTypes 사용 방법 및 용례
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeName;
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
@JsonSubTypes({
@JsonSubTypes.Type(value = Dog.class, name = "DOG"),
@JsonSubTypes.Type(value = Cat.class, name = "CAT")
})
public abstract class Animal {
public String name;
}
@JsonTypeName("DOG")
public class Dog extends Animal {
public double barkVolume;
}
@JsonTypeName("CAT")
public class Cat extends Animal {
public int lives;
}
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeName;
import lombok.NoArgsConstructor;
import java.io.Serializable;
// 기본 인터페이스에 타입 정보와 하위 클래스 매핑 정의
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "animal_type")
@JsonSubTypes({
@JsonSubTypes.Type(value = Dog.class, name = "DOG"),
@JsonSubTypes.Type(value = Cat.class, name = "CAT"),
@JsonSubTypes.Type(value = Bird.class, name = "BIRD")
})
public interface AnimalSpec extends Serializable {
AnimalType getAnimalType();
}
@JsonTypeName("DOG")
@NoArgsConstructor
public class Dog implements AnimalSpec {
@JsonIgnore
private AnimalType animalType = AnimalType.DOG;
private String breed;
private double barkVolume;
@Override
public AnimalType getAnimalType() {
return animalType;
}
// getters and setters
}
@JsonTypeName("CAT")
@NoArgsConstructor
public class Cat implements AnimalSpec {
@JsonIgnore
private AnimalType animalType = AnimalType.CAT;
private String color;
private int lives;
@Override
public AnimalType getAnimalType() {
return animalType;
}
// getters and setters
}
@JsonTypeName("BIRD")
@NoArgsConstructor
public class Bird implements AnimalSpec {
@JsonIgnore
private AnimalType animalType = AnimalType.BIRD;
private String species;
private double wingSpan;
@Override
public AnimalType getAnimalType() {
return animalType;
}
// getters and setters
}
public enum AnimalType {
DOG, CAT, BIRD
}