본문 바로가기

Java

[Json] Jackson을 이용해 추상, 인터페이스 객체 별로 구현체 구분하기

Json String을 구현체별로 매핑할 수 없을까?

- String 형태의 Json을 구현체 모델로 매핑하는 방법은 쉽습니다.
- Json의 프로퍼티 이름과 모델의 필드(Setter/Getter) 이름을 자동으로 매핑해주기 때문입니다.
ㄴ 기존 사용 방법은 생략하겠습니다.

       SampleModelImpl result = new ObjectMapper().readValue(json, SampleModelImpl.class);
- 하지만 인터페이스나 추상클래스 타입이라면 이야기는 달라집니다.
왜냐하면 구현체가 아니다보니 어떤 하위클래스들을 사용할지 모르기 때문입니다.
- 이를 해결하기 위해 Jack에서 다양한 어노테이션들을 제공해주고 있습니다.
- A라는 인터페이스가 있고 B, C라는 구현체가 있다고 가정하면...
- @JsonSubTypes, @JsonTypeInfo를 활용할 수 있습니다.

@JsonTypeInfo(
    use= JsonTypeInfo.Id.NAME,
    include = JsonTypeInfo.As.PROPERTY,
    property = "code"
)
@JsonSubTypes({
    @JsonSubTypes.Type(value = B.class, name = "b"),
    @JsonSubTypes.Type(value = C.class, name = "c"),
})
public interface A {
    String getCode();
}

@JsonTypeName("b")
public class B implements A {
...
...
}

@JsonTypeName("c")
public class C implements A {
...
...
}
- 인터페이스(추상)에 JsonTypeInfo를 통해 어떤 프로퍼티를 구분자로 쓸지 지정합니다.
또한 JsonSubTypes를 통해 해당 값에 따라 구현체로 변환되도록 설정합니다.
- 각 구현체에 JsonTypeName을 붙여 Json String의 값에 따라 구분할 이름을 지정합니다.

    @Test
    public void parseJsonByInterfaceType() throws IOException {
        String json = "{ \"code\" : \"b\" }";

        A result = new ObjectMapper()
            .readValue(json, A.class);
        assertTrue(result instanceof B);

        json = "{ \"code\" : \"c\" }";

        result = new ObjectMapper()
            .readValue(json, A.class);
        assertTrue(result instanceof C);
    }
- Test Code가 잘 성공했네~ 간단하네!

Json String에 구분필드가 없으면 구현체 타입으로 매핑할 때 에러가 발생하게됩니다.

- 구분자 필드로 구분하게 했느데 구분자가 없으니 구현체 타입을 명시해도 에러가 발생합니다. ㅠㅠ
- 이 부분 때문에 좀 삽질을 했는데요. ObjectMapper마다 추가 설정을 해줘야합니다.
- 우선 위에 샘플로 명시했던 인터페이스 설정은 모두 다른 클래스로 이동해야합니다
Jackson의 Mixin을 이용하자

@JsonTypeInfo(
    use= JsonTypeInfo.Id.NAME,
    include = JsonTypeInfo.As.PROPERTY,
    property = "code"
)
@JsonSubTypes({
    @JsonSubTypes.Type(value = B.class, name = "b"),
    @JsonSubTypes.Type(value = C.class, name = "c"),
})
public class Mixin {
}
- 아까 선언했던 인터페이스 어노테이션을 mixin으로 이동합니다.

public interface A {
    String getCode();
}
- 인터페이스는 아무것도 없습니다.
- 대신 이제부터 인터페이스 타입을 객체로 변환할 땐 이 mixin 설정을 objectmapper와 함께 사용할 것입니다.

    @Test
    public void parseJsonByInterfaceType() throws IOException {
        String json = "{ \"code\" : \"b\" }";

        A result = new ObjectMapper()
            .addMixIn(A.class, Mixin.class)
            .readValue(json, A.class);
        assertTrue(result instanceof B);

        json = "{ \"code\" : \"c\" }";

        result = new ObjectMapper()
            .addMixIn(A.class, Mixin.class)
            .readValue(json, A.class);
        assertTrue(result instanceof C);
    }

    @Test
    public void parseJson() throws IOException {
        String json = "{}";

        A result = new ObjectMapper()
            .readValue(json, B.class);
        System.out.println(result);
        assertTrue(result instanceof B);

        result = new ObjectMapper().readValue(json, C.class);
        assertTrue(result instanceof C);
    }
- 첫번째 TC를 보면 addMixIn를 활용해 인터페이스 타입을 구현체로 변화하고 있고
두번째 TC를 보면 addMixin 설정이 없기 때문에 json에 구분필드가 없더라도 구현체 타입으로 잘 변환됩니다.