Notice
Recent Posts
Recent Comments
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Archives
Today
Total
관리 메뉴

DeFacto-Standard IT

jackson-databind Polymorphic Serialization / Deserialization (2) - 3-Depth 본문

Java/References

jackson-databind Polymorphic Serialization / Deserialization (2) - 3-Depth

defacto standard 2019. 3. 13. 00:24

 - Summary

이전 글(https://defacto-standard.tistory.com/796)에서 jackson-databind를 사용한 Polymorphic Serialize / Deserialize를 알아봤다.


이번에는 단순히 인터페이스 - 구체클래스의 관계로 구성되는 2depth Polymorphic Serialize / Deserialize가 아니라,

중간에 한 계층이 더 들어간 3depth Polymorphic Serialize / Deserialize를 살펴보자.


2depth에서는 jackson-databind에서 제공하는 매핑 메타 어노테이션을 사용해

ObjectMapper만을 한 번 래핑하여 특별한 구현 없이 어노테이션 만으로 비교적 쉽게 구현할 수 있었다.


결론적으로, 3depth 부터는 jackson-databind만을 사용해서는 구현할 수 없고, 추가적인 구현이 필요하다.

(https://github.com/FasterXML/jackson-databind/issues/374)



이전 글에 다뤘던, A~C 메시지 종류와 A~CProtocol 간의 2depth Polymorphic Serialize / Deserialize 관계도를 보자.




A는 A끼리만 Serialize / Deserialize된다. B, C 역시 마찬가지다.



만약 다음과 같이 프로토콜이 조금 더 세분화 될 필요가 있다고 해보자.


단순히 A~CProtocl이 아니라, A~CProtocol이 있는 것은 맞으나, 상위 개념으로서 존재하고

A1~3, B1~3, C1~3 이라는 구체적인 프로토콜을 만들어야 하는 상황이라고 가정하자.이를 다음 표에 정리해봤다.


최상위 프로토콜

중간 프로토콜

구체적 프로토콜

Protocol

AProtocol

A1Protocol

A2Protocol

BProtocol

B1Protocol

B2Protocol

B3Protocol

B4Protocol

CProtocol

C1Protocol

C2Protocol

C3Protocol


우선 클라이언트와 통신하는 최상위 프로토콜은 Protocol이다.


그 중간에 프로토콜의 타입은 A, B, C가 있다.


또 그 안에서 여러 개의 구체적인 프로토콜이 존재할 수 있다.



이에 대한 클래스 다이어그램과 관계도는 다음과 같다.



A~CProtocol 만 존재했던 2depth와는 달리 A~CProtocol 역시 상위 개념이 되어버리고,

그 서브 클래스인 A1~2 / B1~4 / C1~3 프로토콜이 실제적으로 사용되는 3depth 구조가 되니 생각보다 내용이 많아졌다.


이전에 작성했던 소스코드를 리팩토링 해보면서 구체적으로 어떻게 3depth Polymorphic Serialize / Deserialize를 구현하는지 살펴보자.


우선 서브 클래스가 많아지고, 구체 클래스였던 A~CProtocol이 추상클래스로 변했으니 이에 따른 클래스 작성 및 구조도를 살펴보자.


먼저 서브클래스가 증가함에 따라 여러 클래스들이 생겨났다. 다음은 이에 맞게 새롭게 구성된 구조도다.



2depth에서는 simple이라는 패키지를 사용했었다.


3depth에서는 클래스의 구현이나 어노테이션의 위치 등이 전혀 달라지므로, 아예 새롭게 작성했다.

공통적으로 사용되는 Message 클래스를 제외하고, 앞으로 사용할 모든 클래스는 complex 패키지 내의 것들을 사용할 것이다.


2depth와 다른 점은 A~CProtocol이 구체적인 클래스에서 상위 클래스로 변경되었고,

이를 상속받는 구체 클래스들이 서브 프로토콜 패키지에 새로 생겼다.


또한 ProtocolType이라는 Enum이 하나 생겼는데, 이는 리팩토링을 진행하면서 알아보겠다.



 - What to do?

크게 보면 2depth Polymorphic Serialize / Deserialize에서 했던 것과 동일하다.


Serialize의 경우 구체 클래스를 하나 만들되, Protocol 타입으로 받아서 ObjectMapper로 Serialize를 수행하고,

반환받은 JSON String을 출력해서 검증한 후에, 이를 다시 ObjectMapper로 Deserialize하는 것이다.


단, Deserialize 시 안타깝게도 Protocol.class라는 정보는 사용되지 않는데(정확히는 사용할 수 없다), 이는 Deserialize에서 자세히 살펴보자.


하지만 Protocol.class라는 정보가 없다고 해서,

ObjectMapper를 사용하는 MessageProtocolConverter의 소스코드에서 구체적인 서브 클래스의 정보가 나오는 것은 아니다.


MessageProtocolConverter는 A1Procotol 등과 같은 구체적인 서브 클래스의 존재를 알지 못해도 된다.



2depth에서는 상위 개념인 Protocol 인터페이스에,

하위 개념인 A~CProtocol에 대한 매핑 메타 어노테이션을 기술하고,

ObjectMapper에 이 매핑 메타 어노테이션이 기술된 Protocol.class를 제공함으로써

Serialize / Deserialize 시 자동으로 프로토콜 타겟 클래스를 찾아낼 수 있었다.


3depth에서도 비슷하게 접근해보자.



 - Serialize


2depth에서 상위 개념인 Protocol 인터페이스에서 메타 매핑 어노테이션을 제공해서 서브 클래스였던 A~CProtocol을 찾을 수 있었다.


따라서 이를 응용해서 다음과 같이 작성해보자.


1. Protocol 인터페이스에 중간 추상 클래스인 A~CProtocol에 대한 메타 매핑 어노테이션을 기술한다.

2. A~CProtocol 추상클래스 각각에 자신을 상속받는 서브 클래스들의 메타 매핑 어노테이션을 기술한다.

3. Protocol 인터페이스 타입의 인자로 ObjectMapper를 통해 Serialize한다.



이를 바탕으로 코드를 작성하면 다음과 같다.


 - Protocol

package complex.protocol;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import complex.protocol.a.AProtocol;
import complex.protocol.b.BProtocol;
import complex.protocol.c.CProtocol;

@JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
@JsonSubTypes({
@JsonSubTypes.Type(value = AProtocol.class, name = "A"),
@JsonSubTypes.Type(value = BProtocol.class, name = "B"),
@JsonSubTypes.Type(value = CProtocol.class, name = "C")
})
public interface Protocol {
void execute();
}

하위의 추상 클래스인 A~CProtocol에 대한 매핑 메타 어노테이션 정보를 가지고 있는 Protocol 인터페이스다.

execute()는 2depth와 마찬가지로 최하위 서브 클래스에 대한 클래스명과 프로퍼티 값을 출력함으로써

Deserialize를 검증하기 위한 메서드이므로 Serialize에서는 설명하지 않는다.



 - AProtocol / BProtocol / CProtocol

package complex.protocol.a;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import complex.protocol.Protocol;
import complex.protocol.a.subprotocol.A1Protocol;
import complex.protocol.a.subprotocol.A2Protocol;

@JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "subType")
@JsonSubTypes({
@JsonSubTypes.Type(value = A1Protocol.class, name = "A1"),
@JsonSubTypes.Type(value = A2Protocol.class, name = "A2")
})
public abstract class AProtocol implements Protocol {
}

package complex.protocol.b;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import complex.protocol.Protocol;
import complex.protocol.b.subprotocol.B1Protocol;
import complex.protocol.b.subprotocol.B2Protocol;
import complex.protocol.b.subprotocol.B3Protocol;
import complex.protocol.b.subprotocol.B4Protocol;

@JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "subType")
@JsonSubTypes({
@JsonSubTypes.Type(value = B1Protocol.class, name = "B1"),
@JsonSubTypes.Type(value = B2Protocol.class, name = "B2"),
@JsonSubTypes.Type(value = B3Protocol.class, name = "B3"),
@JsonSubTypes.Type(value = B4Protocol.class, name = "B4")
})
public abstract class BProtocol implements Protocol {
}

package complex.protocol.c;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import complex.protocol.Protocol;
import complex.protocol.c.subprotocol.C1Protocol;
import complex.protocol.c.subprotocol.C2Protocol;
import complex.protocol.c.subprotocol.C3Protocol;

@JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "subType")
@JsonSubTypes({
@JsonSubTypes.Type(value = C1Protocol.class, name = "C1"),
@JsonSubTypes.Type(value = C2Protocol.class, name = "C2"),
@JsonSubTypes.Type(value = C3Protocol.class, name = "C3")
})
public abstract class CProtocol implements Protocol {
}

2depth에선 A~CProtocol이 최하위 서브 클래스였지만, 지금은 중간에 위치한 클래스다.


이를 상속받는 하위 클래스가 있고, Protocol과 동일하게 메타 매핑 어노테이션을 작성했다.


 - A1/B1/C1Protocol (나머지 생략)

package complex.protocol.a.subprotocol;

import complex.protocol.a.AProtocol;

public class A1Protocol extends AProtocol {
private String a1Field1;
private String a1Field2;

public String getA1Field1() {
return a1Field1;
}

public void setA1Field1(String a1Field1) {
this.a1Field1 = a1Field1;
}

public String getA1Field2() {
return a1Field2;
}

public void setA1Field2(String a1Field2) {
this.a1Field2 = a1Field2;
}

public void execute() {
System.out.println(this.getClass().getSimpleName());
System.out.println("a1Field1 : " + a1Field1 + ", a1Field2 = " + a1Field2);
}
}
package complex.protocol.b.subprotocol;

import complex.protocol.b.BProtocol;

public class B1Protocol extends BProtocol {
private String b1Field1;
private String b1Field2;
private int b1Field3;

public String getB1Field1() {
return b1Field1;
}

public void setB1Field1(String b1Field1) {
this.b1Field1 = b1Field1;
}

public String getB1Field2() {
return b1Field2;
}

public void setB1Field2(String b1Field2) {
this.b1Field2 = b1Field2;
}

public int getB1Field3() {
return b1Field3;
}

public void setB1Field3(int b1Field3) {
this.b1Field3 = b1Field3;
}

public void execute() {
System.out.println(this.getClass().getSimpleName());
System.out.println("b1Field1 : " + b1Field1 + ", b1Field2 = " + b1Field2 + ", b1Field3 = " + b1Field3);
}
}
package complex.protocol.c.subprotocol;

import complex.protocol.c.CProtocol;

public class C1Protocol extends CProtocol {
private String c1Field1;
private String c1Field2;

public String getC1Field1() {
return c1Field1;
}

public void setC1Field1(String c1Field1) {
this.c1Field1 = c1Field1;
}

public String getC1Field2() {
return c1Field2;
}

public void setC1Field2(String c1Field2) {
this.c1Field2 = c1Field2;
}

public void execute() {
System.out.println(this.getClass().getSimpleName());
System.out.println("c1Field1 : " + c1Field1 + ", c1Field2 = " + c1Field2);
}
}

각각 A/B/CProtocol을 상속받는 최하위 프로토콜 클래스다.

2depth에서는 AllArgsConstructor를 사용하다가,

jackson-databind는 getter/setter 네임 기반(자바빈 패턴) 파라미터 바인딩을 지원하므로 NoArgsConstructor를 사용하도록 변경했다.


3depth 역시 자바에서 클래스에 아무런 생성자가 명시되지 않으면 기본 생성자가 제공되므로 명시하지 않았다.


나머지 A2~C3Protocol과 위 프로토콜들과 차이점은 필드의 값이나 갯수 뿐이고, 딱히 다르지 않으므로 생략했다.



 - MessageProtocolConverter

package complex;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import complex.protocol.Protocol;
import message.Message;

import java.io.IOException;

class MessageProtocolConverter {
private ObjectMapper objectMapper;

MessageProtocolConverter() {
objectMapper = new ObjectMapper();
}

/**
* Serialize
**/
Message protocolSerialize(Protocol protocol) {
Message message = new Message();
try {
String json = objectMapper.writeValueAsString(protocol);
message.setMessage(json);
} catch (JsonProcessingException e) {
e.printStackTrace();
System.out.println("Protocol to Message Serialization Failure");
}

return message;
}

/**
* Deserialize
**/
Protocol messageDeserialize(Message message) {
Protocol protocol = null;

try {
protocol = objectMapper.readValue(message.getMessage(), Protocol.class);
} catch (IOException e) {
e.printStackTrace();
System.out.println("Message to Protocol Deserialization Failure");
}

return protocol;

}
}

매핑 정보를 어노테이션으로 적절히 제공했으므로, MessageProtocolConverter는 건드릴 것이 없다.

2depth와 똑같은 로직이지만, Protocol의 import 패키지가 simple에서 complex로 변경되었다.



 - Main

package complex;

import complex.protocol.Protocol;
import complex.protocol.a.subprotocol.A1Protocol;
import complex.protocol.b.subprotocol.B1Protocol;
import complex.protocol.c.subprotocol.C1Protocol;
import message.Message;

public class Main {
public static void main(String[] args) {
MessageProtocolConverter messageProtocolConverter = new MessageProtocolConverter();

System.out.println("-------------Serialize-------------");
Protocol protocolA1 = new A1Protocol()
.setA1Field1("1")
.setA1Field2("2");
Message messageA1 = messageProtocolConverter.protocolSerialize(protocolA1);
System.out.println(messageA1.getMessage());

Protocol protocolB1 = new B1Protocol()
.setB1Field1("1")
.setB1Field2("2")
.setB1Field3(3);
Message messageB1 = messageProtocolConverter.protocolSerialize(protocolB1);
System.out.println(messageB1.getMessage());

Protocol protocolC1 = new C1Protocol()
.setC1Field1("1")
.setC1Field2("2")
Message messageC1 = messageProtocolConverter.protocolSerialize(protocolC1);
System.out.println(messageC1.getMessage());
}
}

MessageProtocolManager를 통해 String (Message 객체) 과 Object (Protocol 객체)간의 Serialize / Deserialize를 검증하는 클래스다.


현재 Serialize만 있으므로 우선 Serialize만 검증할 수 있도록 작성했다.


예제가 너무 길어지므로 우선 A1, B1, C1만 수행하겠다.



우선 Main을 실행시켜보자. 결과는 다음과 같다.


얼핏 보기에는 정상적으로 출력된 것 같다.


하지만 사실 문제가 있다.


subType과 각 필드와 값들은 제대로 출력이 되었지만, 어떤 타입인지 모른다.


예제를 쉽게 구분하기 위해 A는 A대로, B는 B대로, C는 C대로 묶었지만

A1, B1, C1이라는 서브타입이 A의 하위 클래스인지, B의 하위 클래스인지, C의 하위 클래스인지 알 수 없다.


지금처럼 아예 다른 클래스들이라면 별로 문제가 안되지만, 항상 특이한 케이스 역시도 생각해봐야 한다.


AProtocol에 A1이라는 서브 프로토콜이 있으니,

BProtocol에도 똑같이 A1이라는 서브 프로토콜을 작성하고 테스트를 해보자.


다음과 같이 BProtol을 상속받는, AProtocol과는 전혀 상관없는 A1Protocol을 만들자.

package complex.protocol.b.subprotocol;

import complex.protocol.b.BProtocol;

public class A1Protocol extends BProtocol {
private String a1Field1;
private String a1Field2;

public String getA1Field1() {
return a1Field1;
}

public A1Protocol setA1Field1(String a1Field1) {
this.a1Field1 = a1Field1;
return this;
}

public String getA1Field2() {
return a1Field2;
}

public A1Protocol setA1Field2(String a1Field2) {
this.a1Field2 = a1Field2;
return this;
}

public void execute() {
System.out.println(this.getClass().getSimpleName());
System.out.println("a1Field1 : " + a1Field1 + ", a1Field2 = " + a1Field2);
}
}


그리고 BProtocol에 A1Protocol에 대한 매핑 정보를 추가하자

package complex.protocol.b;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import complex.protocol.Protocol;
import complex.protocol.b.subprotocol.*;

@JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "subType")
@JsonSubTypes({
@JsonSubTypes.Type(value = B1Protocol.class, name = "B1"),
@JsonSubTypes.Type(value = B2Protocol.class, name = "B2"),
@JsonSubTypes.Type(value = B3Protocol.class, name = "B3"),
@JsonSubTypes.Type(value = B4Protocol.class, name = "B4"),
@JsonSubTypes.Type(value = A1Protocol.class, name = "A1"),
})
public abstract class BProtocol implements Protocol {
}


그리고 Main을 다음과 같이 수정하자.

package complex;

import complex.protocol.Protocol;
import complex.protocol.a.subprotocol.A1Protocol;
import complex.protocol.b.subprotocol.B1Protocol;
import complex.protocol.c.subprotocol.C1Protocol;
import message.Message;

public class Main {
public static void main(String[] args) {
MessageProtocolConverter messageProtocolConverter = new MessageProtocolConverter();

System.out.println("-------------Serialize-------------");
Protocol protocolA1 = new complex.protocol.a.subprotocol.A1Protocol()
.setA1Field1("1")
.setA1Field2("2");
Message messageA1 = messageProtocolConverter.protocolSerialize(protocolA1);
System.out.println(messageA1.getMessage());

Protocol protocolB1 = new complex.protocol.b.subprotocol.A1Protocol()
.setA1Field1("1")
.setA1Field2("2");
Message messageB1 = messageProtocolConverter.protocolSerialize(protocolB1);
System.out.println(messageB1.getMessage());

Protocol protocolB2 = new B1Protocol()
.setB1Field1("1")
.setB1Field2("2")
.setB1Field3(3);
Message messageB2 = messageProtocolConverter.protocolSerialize(protocolB2);
System.out.println(messageB2.getMessage());

Protocol protocolC1 = new C1Protocol()
.setC1Field1("1")
.setC1Field2("2");
Message messageC1 = messageProtocolConverter.protocolSerialize(protocolC1);
System.out.println(messageC1.getMessage());
}
}


수행 결과는 다음과 같다.



처음 subType이 A1인 것은 AProtocol을 상속받는 A1Protocol이며

두 번째 subType이 A1인 것은 BProtocol을 상속받는 A1Protocol이다.


분명 두 프로토콜은 다른 클래스로부터 Serialize된 문자열이다.

하지만 이를 받아서 사용하는 클라이언트 입장에서, 이게 정확하게 AProtocol의 일종인지 BProtocol의 일종인지 알 수 있는 방법이 없다.


물론 필드명을 다르게 줘서 구별할 수도 있지만,

클라이언트 단에서는 if-else 분기분을 json node의 key 값을 바탕으로 계속해서 체크해야 하므로 굉장히 복잡해질 것이다.


만약 다른 방법으로 서브 타입이 겹치지 않아야 한다는 관례를 둔다면

서브 타입 프로토콜을 만들 때마다 이 프로토콜이 다른 프로토콜에 존재하는지 존재하지 않는지 일일이 살펴봐야 할 것이다.


따라서 결국 type이라는 문자를 출력하는 것이 근본적인 해결방법이다.



그나저나 type 키는 왜 나오지 않았을까? 이유는 간단하다.


이 글 맨 위에서 말했듯이, jackson-databind는 multi-level Polymorphic Serialize / Deserialize를 지원하지 않기 때문이다.



그렇다면 Protocol 변수로 받아서 넘겼고, Protocol에 매핑 메타 어노테이션이 기술되어 있는데

어째서 Protocol에 기술되어 있는 A~CProtocol에 대한 정보 없이 바로 최하위 서브 클래스의 정보가 출력이 됐을까.


이것은 jackson-databind 보다는 Java를 설명해야 한다.

Java에서 업 캐스팅을 한다고 해도, JVM Heap에는 new 키워드로 만들어낸 구체적인 서브 클래스 객체의 내용이 그대로 담겨있다.


다만 업 캐스팅을 하면 개발자 입장에서는 인터페이스를 다룰 때

해당 인터페이스에만 정의되어 있는 메서드를 부를 수 있으므로 하위 클래스 객체는 접근할 수 없다는 오해를 할 수 있다.


만약 이게 사실이라면, 자바에 다형성이란 것 자체가 존재할 수 없다.


다형성이란 상위 객체를 다루도록 소스코드를 작성하지만, 내부에서는 구체적인 클래스가 실행되는 원리를 이용한 것이다.


따라서 업캐스팅은 객체지향에서 개발자와 소스코드의 품질을 높이기 위한 수단일 뿐이다.


따라서 특정 인터페이스를 구현하는 클래스에 대해,

인터페이스 타입으로 받아서(업캐스팅) Serailize를 한다고 하더라도 JVM Heap에 있는 객체를 대상으로 수행하기 때문에

정보가 온전히 출력되는 것이다.


사실 이는 자바의 레퍼런스와 객체의 차이점을 알고 있다면 당연한 것이다.




그렇다면, type을 출력하기 위해서는 구체적인 서브 클래스가 상속받는 A~CProtocol에서 제공해주면 되지 않을까.


이 가설을 바탕으로 다시 Serialize해보자.



여러 가지 방법이 있을 수 있는데, 당장 생각나는 것부터 적어보자.


1. 구체 클래스를 생성할 때 생성자 또는 setter를 통해 A~CProtocol에 대한 type정보(가령 "A"라는 값)를 저장시키고,

getter를 기술하여 Serialize 시 getter 메서드를 바탕으로 얻을 수 있게 한다.


2. A~CProtocol 추상클래스에서 제공한다.


1번 방법의 경우 딱봐도 구체클래스를 만들때마다 "A"라는 값을 넣어줘야 할 것 같다.

따라서 2번 방법으로 먼저 시도해보자.


빠른 테스트를 위해 public 필드로 선언하는 것도 좋지만, 여러 개의 클래스를 수정해야 하는 작업인 만큼 실수하지 않도록 확실하게 하자.


private field와 getter를 사용해 테스트해보자.


다음은 이를 바탕으로 수정된 A~CProtocol이다.


 - AProtocol, BProtocol, CProtocol

package complex.protocol.a;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import complex.protocol.Protocol;
import complex.protocol.ProtocolType;
import complex.protocol.a.subprotocol.A1Protocol;
import complex.protocol.a.subprotocol.A2Protocol;

@JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "subType")
@JsonSubTypes({
@JsonSubTypes.Type(value = A1Protocol.class, name = "A1"),
@JsonSubTypes.Type(value = A2Protocol.class, name = "A2")
})
public abstract class AProtocol implements Protocol {
public ProtocolType getType() {
return ProtocolType.A;
}
}
package complex.protocol.b;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import complex.protocol.Protocol;
import complex.protocol.ProtocolType;
import complex.protocol.b.subprotocol.*;

@JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "subType")
@JsonSubTypes({
@JsonSubTypes.Type(value = B1Protocol.class, name = "B1"),
@JsonSubTypes.Type(value = B2Protocol.class, name = "B2"),
@JsonSubTypes.Type(value = B3Protocol.class, name = "B3"),
@JsonSubTypes.Type(value = B4Protocol.class, name = "B4"),
@JsonSubTypes.Type(value = A1Protocol.class, name = "A1"),
})
public abstract class BProtocol implements Protocol {
public ProtocolType getType() {
return ProtocolType.B;
}
}
package complex.protocol.c;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import complex.protocol.Protocol;
import complex.protocol.ProtocolType;
import complex.protocol.c.subprotocol.C1Protocol;
import complex.protocol.c.subprotocol.C2Protocol;
import complex.protocol.c.subprotocol.C3Protocol;

@JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "subType")
@JsonSubTypes({
@JsonSubTypes.Type(value = C1Protocol.class, name = "C1"),
@JsonSubTypes.Type(value = C2Protocol.class, name = "C2"),
@JsonSubTypes.Type(value = C3Protocol.class, name = "C3")
})
public abstract class CProtocol implements Protocol {
public ProtocolType getType() {
return ProtocolType.C;
}
}

private field를 두고 여기에 ProcotolType.Enum 을 저장하고 public getter에서 private field를 리턴하도록 기술해도 되지만,

어차피 특정 클래스에서 특정 값만 리턴할 것이라면 public gettter에서 바로 리턴하는 것이 나을 것 같다.



Main을 수행시켜보면 다음과 같다.



만약 서브 클래스가 여러 클래스를 상속받았다고 해보자.

(한 번에 여러 클래스는 상속받을 수 없으니, 상속에 상속에 상속에 상속을 받은 경우를 말한다)


jackson-databind를 사용하는 경우 상위 클래스에서 public getter만 인식한다.

서브 클래스는 상위 클래스의 protected 메서드 역시 상속받아 사용할 수 있는데, jackson-databind사용 시 상위 메서드의 protected getter는 인식하지 않는다.


protected를 쓰면서 인식하게 하려면 하위 클래스에서 오버라이딩해야 한다.



아무튼 우선 정상적으로 Serialize가 되는 것 같다.


하지만 추상 클래스에서 자신이 제공하는 type값을 Enum을 통해 제공하는 것은 납득할만 하지만,

이렇게 하드코딩되어 있으면 이후에 변경이 생겼을 때 일일이 찾아서 수정해줘야 할 것 같다.


Enum으로 바꾸는 것은 이후에 생각하고 우선 Serialize가 생각처럼 잘 된다는 것에 만족하고 Deserialize로 넘어가도록 하자.




 - Deserialize

Serialize는 추상 클래스에 해당 추상 클래스를 상속하는 서브 클래스가 일일이 type값을 주지 않기 위해 추상 클래스에서 직접 필드를 설정하고 public getter를 설정하니 제대로 동작했다.


우선 아무것도 건드리지 말고 바로 Deserialize 를 해보자.


2depth와 마찬가지로 만들어진 문자열을 바탕으로 Deserialize해보자.


main은 다음과 같다.

package complex;

import complex.protocol.Protocol;
import complex.protocol.b.subprotocol.B1Protocol;
import complex.protocol.c.subprotocol.C1Protocol;
import message.Message;

public class Main {
public static void main(String[] args) {
MessageProtocolConverter messageProtocolConverter = new MessageProtocolConverter();

System.out.println("-------------Serialize-------------");
Protocol protocolA1 = new complex.protocol.a.subprotocol.A1Protocol()
.setA1Field1("1")
.setA1Field2("2");
Message messageA1 = messageProtocolConverter.protocolSerialize(protocolA1);
System.out.println(messageA1.getMessage());

Protocol protocolB1 = new complex.protocol.b.subprotocol.A1Protocol()
.setA1Field1("1")
.setA1Field2("2");
Message messageB1 = messageProtocolConverter.protocolSerialize(protocolB1);
System.out.println(messageB1.getMessage());

Protocol protocolB2 = new B1Protocol()
.setB1Field1("1")
.setB1Field2("2")
.setB1Field3(3);
Message messageB2 = messageProtocolConverter.protocolSerialize(protocolB2);
System.out.println(messageB2.getMessage());

Protocol protocolC1 = new C1Protocol()
.setC1Field1("1")
.setC1Field2("2");
Message messageC1 = messageProtocolConverter.protocolSerialize(protocolC1);
System.out.println(messageC1.getMessage());

System.out.println("-------------Deserialize-------------");
Protocol deserializedProtocolA1 = messageProtocolConverter.messageDeserialize(messageA1);
deserializedProtocolA1.execute();

Protocol deserializedProtocolB1 = messageProtocolConverter.messageDeserialize(messageB1);
deserializedProtocolB1.execute();

Protocol deserializedProtocolB2 = messageProtocolConverter.messageDeserialize(messageB2);
deserializedProtocolB2.execute();

Protocol deserializedProtocolC1 = messageProtocolConverter.messageDeserialize(messageC1);
deserializedProtocolC1.execute();

}
}


결과는 다음과 같다.

-------------Serialize-------------

{"subType":"A1","a1Field1":"1","a1Field2":"2"}

{"subType":"A1","a1Field1":"1","a1Field2":"2"}

{"subType":"B1","b1Field1":"1","b1Field2":"2","b1Field3":3}

{"subType":"C1","c1Field1":"1","c1Field2":"2"}

-------------Deserialize-------------

com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Missing type id when trying to resolve subtype of [simple type, class complex.protocol.Protocol]: missing type id property 'type'

 at [Source: (String)"{"subType":"A1","a1Field1":"1","a1Field2":"2"}"; line: 1, column: 46]

at com.fasterxml.jackson.databind.exc.InvalidTypeIdException.from(InvalidTypeIdException.java:43)

at com.fasterxml.jackson.databind.DeserializationContext.missingTypeIdException(DeserializationContext.java:1645)

at com.fasterxml.jackson.databind.DeserializationContext.handleMissingTypeId(DeserializationContext.java:1218)

at com.fasterxml.jackson.databind.jsontype.impl.TypeDeserializerBase._handleMissingTypeId(TypeDeserializerBase.java:300)

at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer._deserializeTypedUsingDefaultImpl(AsPropertyTypeDeserializer.java:164)

at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer.deserializeTypedFromObject(AsPropertyTypeDeserializer.java:105)

at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserializeWithType(AbstractDeserializer.java:254)

at com.fasterxml.jackson.databind.deser.impl.TypeWrappedDeserializer.deserialize(TypeWrappedDeserializer.java:68)

at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4013)

at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3004)

at complex.MessageProtocolConverter.messageDeserialize(MessageProtocolConverter.java:40)

at complex.Main.main(Main.java:39)

Message to Protocol Deserialization Failure

Exception in thread "main" java.lang.NullPointerException

at complex.Main.main(Main.java:40)



type이라는 값을 못찾는다고 한다.


2depth 에서도 살펴봤듯이 jackson-databind는 private field에 데이터를 바인딩하기 위해서는 setter가 필요했었다.


현재 추상 클래스 모두가 각각 값을 가지고 있지만, 추상 클래스에 모두 public setter를 추가하고 다시 실행해보자.


실행 결과는 다음과 같다.

-------------Serialize-------------

{"subType":"A1","type":"A","a1Field1":"1","a1Field2":"2"}

{"subType":"A1","type":"B","a1Field1":"1","a1Field2":"2"}

{"subType":"B1","type":"B","b1Field1":"1","b1Field2":"2","b1Field3":3}

{"subType":"C1","type":"C","c1Field1":"1","c1Field2":"2"}

-------------Deserialize-------------

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `complex.protocol.a.AProtocol` (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information

 at [Source: (String)"{"subType":"A1","type":"A","a1Field1":"1","a1Field2":"2"}"; line: 1, column: 24]

at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)

at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1452)

at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1028)

at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserialize(AbstractDeserializer.java:265)

at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer._deserializeTypedForId(AsPropertyTypeDeserializer.java:130)

at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer.deserializeTypedFromObject(AsPropertyTypeDeserializer.java:97)

at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserializeWithType(AbstractDeserializer.java:254)

at com.fasterxml.jackson.databind.deser.impl.TypeWrappedDeserializer.deserialize(TypeWrappedDeserializer.java:68)

at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4013)

at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3004)

at complex.MessageProtocolConverter.messageDeserialize(MessageProtocolConverter.java:40)

at complex.Main.main(Main.java:39)

Message to Protocol Deserialization Failure

Exception in thread "main" java.lang.NullPointerException

at complex.Main.main(Main.java:40)



예상과는 다르게 동작했다.


no Creators, like default construct,  라는 문구를 보고

추상 클래스에 기본 생성자를 작성하더라도 문제가 발생한다.

서브 클래스에서 super()를 호출해도 문제가 발생한다.


이것이 jackson-databind를 사용해서 multi-level Polymorphic Deserialize를 할 때의 문제다.


Serialize는 객체를 그대로 파싱해서 뿌려주기만 하면 되므로 생각보다 간단히 해결됐지만,

Deserialize의 경우 스트링을 매핑된 객체에 따라 다르게 넣어주어야 한다.


근데 아무리 메타 매핑 어노테이션을 추상 클래스나 인터페이스에 전부 걸쳐서 작성을 하더라도, multi-level은 지원하지 않는다.




여기서 여러 가지 해결방법이 있을 수 있다.


CustomDeserialize를 구현하는 방법도 있고, 구체적인 클래스 정보를 Enum값에 따라 넘겨주는 방법도 있다.


CustomDeserialize를 사용하는 방법은 다소 복잡하게 느껴지므로, Enum 값을 통해 해결하는 방법을 사용하겠다.



우선 위에서 추상클래스에서 자기 자신이 가지고 있던 "A~C"라고 하드코딩되어 있는 값들을 Enum으로 관리하자.


ProtocolType이라는 Enum 타입을 정의하자.

package complex.protocol;

public enum ProtocolType {
A, B, C
}


그리고 각 추상 클래스에서 이 ProtocolType.A~C를 사용해서 값을 가져오도록 하자.

또한 setter를 통한 deserialize는 불가능하니 삭제하도록 하자.

getter는 String 대신 ProtocolType 타입으로 변환하도록 수정하자.

package complex.protocol.a;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import complex.protocol.Protocol;
import complex.protocol.ProtocolType;
import complex.protocol.a.subprotocol.A1Protocol;
import complex.protocol.a.subprotocol.A2Protocol;

@JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "subType")
@JsonSubTypes({
@JsonSubTypes.Type(value = A1Protocol.class, name = "A1"),
@JsonSubTypes.Type(value = A2Protocol.class, name = "A2")
})
public abstract class AProtocol implements Protocol {
private ProtocolType type = ProtocolType.A;

public ProtocolType getType() {
return type;
}
}
package complex.protocol.b;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import complex.protocol.Protocol;
import complex.protocol.ProtocolType;
import complex.protocol.b.subprotocol.*;

@JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "subType")
@JsonSubTypes({
@JsonSubTypes.Type(value = B1Protocol.class, name = "B1"),
@JsonSubTypes.Type(value = B2Protocol.class, name = "B2"),
@JsonSubTypes.Type(value = B3Protocol.class, name = "B3"),
@JsonSubTypes.Type(value = B4Protocol.class, name = "B4"),
@JsonSubTypes.Type(value = A1Protocol.class, name = "A1"),
})
public abstract class BProtocol implements Protocol {
private ProtocolType type = ProtocolType.B;

public ProtocolType getType() {
return type;
}
}
package complex.protocol.c;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import complex.protocol.Protocol;
import complex.protocol.ProtocolType;
import complex.protocol.c.subprotocol.C1Protocol;
import complex.protocol.c.subprotocol.C2Protocol;
import complex.protocol.c.subprotocol.C3Protocol;

@JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "subType")
@JsonSubTypes({
@JsonSubTypes.Type(value = C1Protocol.class, name = "C1"),
@JsonSubTypes.Type(value = C2Protocol.class, name = "C2"),
@JsonSubTypes.Type(value = C3Protocol.class, name = "C3")
})
public abstract class CProtocol implements Protocol {
private ProtocolType type = ProtocolType.C;

public ProtocolType getType() {
return type;
}
}


이 상태에서 수행을 하면 String을 사용하기 전과 똑같은 오류가 난다.

-------------Serialize-------------

{"subType":"A1","type":"A","a1Field1":"1","a1Field2":"2"}

{"subType":"A1","type":"B","a1Field1":"1","a1Field2":"2"}

{"subType":"B1","type":"B","b1Field1":"1","b1Field2":"2","b1Field3":3}

{"subType":"C1","type":"C","c1Field1":"1","c1Field2":"2"}

-------------Deserialize-------------

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `complex.protocol.a.AProtocol` (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information

 at [Source: (String)"{"subType":"A1","type":"A","a1Field1":"1","a1Field2":"2"}"; line: 1, column: 24]

at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)

at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1452)

at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1028)

at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserialize(AbstractDeserializer.java:265)

at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer._deserializeTypedForId(AsPropertyTypeDeserializer.java:130)

at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer.deserializeTypedFromObject(AsPropertyTypeDeserializer.java:97)

at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserializeWithType(AbstractDeserializer.java:254)

at com.fasterxml.jackson.databind.deser.impl.TypeWrappedDeserializer.deserialize(TypeWrappedDeserializer.java:68)

at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4013)

at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3004)

at complex.MessageProtocolConverter.messageDeserialize(MessageProtocolConverter.java:40)

at complex.Main.main(Main.java:39)

Message to Protocol Deserialization Failure

Exception in thread "main" java.lang.NullPointerException

at complex.Main.main(Main.java:40)



이제 본격적으로 수정을 해보자.


실제로 에러가 나는 부분은 MessageProtocolConverter.messageDeserialize() 의 ObjectMapper.readValue() 이다.


Message의 string 값은 적절하게 Serialize되고 있다. 아무래도 Protocol.class를 인자로 넘기는 쪽이 문제인 것 같다.



jackson-databind는 하위 클래스가 추상클래스인 경우를 지원하지 않는다. 즉, 무조건 최하위 서브 클래스여야 한다.


현재는 Protocol을 직접적으로 상속받는 클래스는 A~CProtocol이라는 추상적인 클래스다.

Protocol의 매핑 정보 메타 어노테이션의 서브 클래스로 추상 클래스의 정보가 제공되기 때문에 오류가 나는 것이다.

아마 자바에서는 추상 클래스의 타입으로는 인스턴스를 만들 수 없기 때문에 에러가 났던 것 같다.



따라서 Protocol.class가 아닌 직접적인 서브 클래스의 정보를 넣어줄 필요가 있다.


하지만 직접적인 서브 클래스를 사용하기엔 유지보수성이 크게 떨어져 부담이 된다.

따라서 String을 직접 파싱하여 해결해야 할 것 같다.


jackson-databind에서 제공하는 ObjectMapper.readTree()가 리턴하는 JsonNode 타입를 활용해서

type이라는 키 값에 해당하는 value 값을 읽고,

이 type값을 바탕으로 Enum 에서 뒤져서 해당 클래스와 매치되는 추상 클래스의 정보를 넘기도록 변경하면 될 것 같다.

추상클래스의 정보가 넘어가면 A~CProtocol에는

각 추상 클래스가 가지는 서브 클래스들에 대한 매핑 메타 어노테이션이 있기 때문에 제대로 동작할 것이라 예상된다.


참고로, CustomDeserialize의 경우도 대부분 이런 식으로 구현한다.

별로 마음에 들지는 않지만, jackson-databind를 사용한다면 이런 방식이 한계인 것 같다.


한마디로, 이 방법은 Enum와 CustomDeserialize의 혼합버전이라고 생각하면 된다.

크게 보면 CustomDeserializer를 구현하는 것과 같다.

(만약 더 좋은 해결 방법이 있다면 공유 부탁드립니다.)



ProtocolType은 넘어온 문자열에 대해 Enum을 리턴하고,

해당 Enum에 정의되어 있는 getProtocolType()이라는 메서드를 통해 추상 클래스 정보를 얻어보자.


그리고 이 추상클래스 정보를 ObjectMapper.readValue()의 2번째 인자에

Protocol.class대신 AProtocol.class 또는 BProtocol.class 등이 추상 클래스의 Object 타입이 자동으로 선택되어 들어가게 해보자.


이렇게 한다면 DProtocol 등이 추가되어도 수정할 부분이 크게 많지 않아

Polymorphic Serialize / Deserialize와 유지보수를 동시에 어느정도 챙길 수 있을 것 같다.


우선 ProtocolType을 수정하자.

package complex.protocol;

import complex.protocol.a.AProtocol;
import complex.protocol.b.BProtocol;
import complex.protocol.c.CProtocol;

public enum ProtocolType {
A(AProtocol.class),
B(BProtocol.class),
C(CProtocol.class);

private Class clazz;

ProtocolType(Class clazz) {
this.clazz = clazz;
}

public Class getProtocolType() {
return this.clazz;
}
}


ProtocolType의 Enum값은 json String의 'type'에 해당하는 밸류 값들이 정의된다. 'type'에 들어갈 밸류의 도메인은 현재 A, B, C다.


그리고 기본적으로 각 type에 해당되는 밸류 값들에 매치되는 Class 타입을 생성자로 넘기도록 하자.

각 Enum은 private으로 해당 Class 타입 정보를 저장하고, getProtocolType() 메서드를 호출하면 각 Class 정보를 넘겨줄 것이다.


자바의 모든 Enum에는 .valueOf(String string) 이라는 메서드가 구현되어 있기 때문에, String 값을 기반으로 Enum을 구할 수 있다.


getProtocolType()의 리턴 타입으로 String이나 다른 타입이 아닌 Class를 사용하는 이유는, 

ObjectMapper에 오버로딩된 여러 메서드 중 readValue(String content, Class<T> valueType) 를 사용하기 위함이다.



ProtocolType이 적절한 클래스 정보를 넘길 수 있도록 수정되었으니 MessageProtocolConverter에서 이를 지원하도록 수정해보자.

package complex;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import complex.protocol.Protocol;
import complex.protocol.ProtocolType;
import message.Message;

import java.io.IOException;

class MessageProtocolConverter {
private ObjectMapper objectMapper;

MessageProtocolConverter() {
objectMapper = new ObjectMapper();
}

/**
* Serialize
**/
Message protocolSerialize(Protocol protocol) {
Message message = new Message();
try {
String json = objectMapper.writeValueAsString(protocol);
message.setMessage(json);
} catch (JsonProcessingException e) {
e.printStackTrace();
System.out.println("Protocol to Message Serialization Failure");
}

return message;
}

/**
* Deserialize
**/
Protocol messageDeserialize(Message message) {
try {
// json 메시지를 json node 타입으로 변경한다.
JsonNode protocolJsonNode = objectMapper.readTree(message.getMessage());

// 변경된 JsonNode 중 "type"을 키 값으로 하는 JSON Node를 얻음
JsonNode typeJsonNode = protocolJsonNode.get("type");

// type 키에 해당되는 value값을 String으로 저장
String stringProtocolType = typeJsonNode.textValue();

// ProtocolType에서 해당 String 값을 바탕으로 Enum을 구함
ProtocolType protocolType = ProtocolType.valueOf(stringProtocolType);

// 해당 Enum의 getProtocolType()을 호출하면 추상 클래스의 정보가 넘어온다.
// messageDeserialize() 메서드의 리턴타입은 Protocol인데,
// readValue()는 Class<T>를 리턴하므로 Protocol 타입으로 강제로 캐스팅한다.
return (Protocol) objectMapper.readValue(message.getMessage(), protocolType.getProtocolType());
} catch (IOException e) {
System.out.println("Mesaage to Protocol Deserialization Failure");
}
return null;
}
}


Main 메서드를 수행하기 전에, Protocol의 메타 매핑 정보 어노테이션은 더 이상 쓸 수 없기 때문에 삭제하도록 하자.

package complex.protocol;

public interface Protocol {
void execute();
}



이제 Main 메서드를 다시 수행해보자.



깔끔하게 3depth Serialize / Deserialize가 수행되는 것을 확인할 수 있다.


모든 경우의 수를 테스트해보자.



깔끔하게 동작한다.


사실 CustomDeserializer 부분(MessageProtocolConverter)을 보면 알겠지만, 엄연히 말하자면 100% Polymorphic Deserialize는 아니다.

하지만 jackson-databind를 사용한다면, 100% 지원을 하는 것이 아니기 때문에 위와 같이 구현해야 한다.


하지만 Deserialize에 필요한 키 값만 일치시키고 해당 키 값을 구해서 Enum으로 일괄적으로 처리하게 한다면,

그리고 위 예제의 MessageProtocolConverter를 조금만 더 구조있게 수정한다면,

3depth 뿐 아니라 n-depth 까지 처리가 가능하도록 응용할 수 있을 것이다.


Comments