Deserialize Generic

com.fasterxml.jackson 을 사용하여 serialize/deserialize 를 수행한다. Generic Type 의 경우 어떻게 deserialize 할 수 있는지 알아보자.

ObjectMapper 의 deserialize 를 수행하는 readValue 메서드를 살펴보면 위와 같은 코드가 존재한다.

2가지 방법으로 deserialize 를 수행할 수 있다.

TypeReference

public abstract class TypeReference<T> implements Comparable<TypeReference<T>> {
    protected final Type _type;

    protected TypeReference() {
        Type superClass = this.getClass().getGenericSuperclass();
        if (superClass instanceof Class) {
            throw new IllegalArgumentException("Internal error: TypeReference constructed without actual type information");
        } else {
            this._type = ((ParameterizedType)superClass).getActualTypeArguments()[0];
        }
    }

    public Type getType() {
        return this._type;
    }

    public int compareTo(TypeReference<T> o) {
        return 0;
    }
}

this._type = ((ParameterizedType)superClass).getActualTypeArguments() 이 코드가 핵심인데, 일반 유형 정보를 보존하기 위해 Super Type Tokens 라는 개념을 사용하게 된다.

단계 별로 살펴보자.

TypeReference<Map<String, Integer>> token = new TypeReference<Map<String, String>>() {};

먼저 위 처럼 익명 클래스를 생성한다. (Anonymous classes are inner classes with no name.)

Inner Class 이기 때문에 자신을 둘러싼 클래스에 대해서 참조를 갖게 된다.

  1. Super Class Metadata 가져오기 - TypeReference<Map<String, Integer>>
  2. Super Class 에 대한 actual type parameter 가져오기 - Map<String, Integer>

This approach for preserving the generic type information is usually known as super type token:

TypeReference<Map<String, Integer>> token = new TypeReference<Map<String, Integer>>() {};
Type type = token.getType();

assertEquals("java.util.Map<java.lang.String, java.lang.Integer>", type.getTypeName());

Type[] typeArguments = ((ParameterizedType) type).getActualTypeArguments();
assertEquals("java.lang.String", typeArguments[0].getTypeName());
assertEquals("java.lang.Integer", typeArguments[1].getTypeName());

JavaType

아래와 같이 JavaType 을 활용하여 deserialize 를 할 수 있다.

JavaType javaType = objectMapper.getTypeFactory().constructParametricType(JsonResponse.class, User.class);
JsonResponse<User> jsonResponse = objectMapper.readValue(json, javaType);

How to deserialize various types

Function:

private inline fun <reified T> deserialize(
    source: String,
    target: TypeReference<T>
): T {
    return try {
        objectMapper.readValue(source, target)
    } catch (e: Exception) {
        log.error("# [deserialize] - stacktrace: {}", e.stackTrace)
        throw DeserializeException()
    }
}

Usage:

deserialize(source = source, target = object: TypeReference<BaseResponse<XXX>>() {})