객체의 직렬화, 역직렬화에서 가장 선호하는 방법은 JSON(JavaScript Object Notation)이다. 일단 JSON 포맷의 확장성과 간결함이 좋다. 그리고 웬만한 포맷들은 JSON을 지원하여 변환이 가능하다. 이전 회사에서 .NET 기반으로 개발할 때 JSON.NET의 만족도가 매우 높은 이유도 있다. 사실 JSON.NET이 너무 좋아서 Java로 넘어오며 매우 아쉬운 점이 많다.

Jackson vs GSON

Guava vs Apache Commons 보다 어려운 고민이다. 왜냐하면 거의 사용성, 성능은 비슷한 느낌이다.

  • Jackson
    • 다목적으로 사용 가능한 JSON 처리 라이브러리
    • Java 세상의 최고 갑인 Spring에서 사용하는 JSON 라이브러리
  • GSON
    • Guava를 만든 구글, 그 이름을 믿고 사용하는 GSON
    • JEST 의존 라이브러리

프로젝트에 직접 사용하며 느낀 점은 두 라이브러리는 너무나 유사하고 비교하기 어렵다. Jackson의 경우 다양한 추가 모듈이 존재해서 JSON을 Avro, BSON, CBOR, CSV, Smile, (Java) Properties, Protobuf, XML or YAML으로 저장하기 등의 추가 기능으로 확장하기 좋다. 그래서 의존성 패키지들이 덕지 덕지 붙긴하지만 편하다.

그래서 계속 써야한다면 개인적인 생각으로 Jackson 유용하다고 생각한다. GSON이 가볍고 편리하다고 하지만 Java, Spring을 사용한다면 Jackson에 대한 의존성을 이미 가지고 있다. 이런 경우에는 오히려 Jackson을 쓰는 것이 더 유용하였다.

Jackson

Jackson은 여러 모듈로 세분화 되어 있고 실제로 사용하는 경우에는 대부분 databind 모듈를 의존한다.

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.3</version>
</dependency>

Maven 기준으로 위의 설정이면 Jackson이 사용 가능하다. 기본적인 Bean Style의 getter/setter를 맞춰준다면 JSON 변환이 가능하다.

Modules

Jackson을 사용하는 이상 Modules를 적극적으로 활용하면 여러 포맷을 지원 가능하다. 자주 사용하는 모듈은 다음과 같다.

Java의 JSON 라이브러리는 조금 아쉽다!

.NET의 JSON.NET을 사용하다 Jackson을 처음 사용하면서 느낀 점은 '아... 안되는게 너무 많다.' 였다. 사실 Java 언어 스펙 자체와 Reflection의 한계로 인한 요소들이 많았다.

생성자를 이용한 Deserialize (Good-bye Bean Style)

왜? Reflection이 완벽하지 않은가? Lombok에 대한 의존성(특히 개발툴 설정까지 맞춰야 한다는!)이 너무 싫지만 Java Bean 형태를 제공하기 위해서 get/set과 constructor에 Annotation 이용하여 선언을 해야하는 점이 너무 불편하다. (개인적으로 생성자 파라미터로 값을 받는 구조를 선호한다)

@JsonCreator
public Person(@JsonProperty("id") String id,
              @JsonProperty("age") int age) {
    this.id = id;
    this.age = age;
}

<Constructor를 이용한 Jackson Deserialize 방법>

하지만 이렇게라도 생성자를 이용하여 Deserializing이 가능한 것이 다행이다. 물론 jdk-8부터 제공하는 --parameters 옵션과 jackson-module-parameter-names을 이용하면 @JsonProperty 사용 없이 사용이 가능하다. 하지만 실제로 해볼 때, 상속/Inner-class 등 복잡해진 클래스는 잘 되지 않는다. 이게 뭐야~~ :()

Polymorphism

인터페이스 혹은 추상 클래스를 멤버로 가진 경우를 JSON으로 저장하는 경우, 왜 이랬을까? 좀 이상하다. 이 때는 Event를 하나의 Table에 일관된 포맷으로 넣고 싶었기 때문에 Payload부분을 모두 JSON으로 저장하여 처리하였다. 결국 다형성을 JSON으로 표현할 때 Annotation을 남발하여 정의하였다.

@JsonTypeInfo(
        use = JsonTypeInfo.Id.CLASS,
        include = JsonTypeInfo.As.PROPERTY,
        property = "@class")
public class Person {
    private final String id;
    private final int age;
...
}

이 외에도 Default Typing, Mixin을 이용한 방법도 존재한다. 참고[2], [3]에서 자세한 방법이 기록되어 있다.

사실 라이브러리 내에서 최대한 해결하여 도메인 내용을 포함하는 클래스에까지 Annotation을 추가하고 싶지 않지만 다형성을 지원하기 위해서는 가장 쉬운 방법이다. 프로젝트에 적합한 Serializer/Deserializer를 구현하여 이러한 타입까지 처리하는 방법이 더 좋다고 생각한다.

참고자료