Post

Java Jackson 동적 JSON 매핑

1. JsonNode 사용

웹샵에서 제품 사양을 처리하고 싶다고 가정한다. 모든 제품에는 몇 가지 공통된 특성이 있지만 제품 유형에 따라 다른 특성도 있다.

예를 들어, 휴대폰 디스플레이의 종횡비를 알고 싶지만 신발에는 이 속성이 별로 의미가 없다.

데이터 구조는 다음과 같다.

1
2
3
4
5
6
7
8
{
    "name": "Pear yPhone 72",
    "category": "cellphone",
    "details": {
        "displayAspectRatio": "97:3",
        "audioConnector": "none"
    }
}

세부정보 개체에 동적 속성을 저장한다.

다음 Java 클래스를 사용하여 공통 속성을 매핑할 수 있다.

1
2
3
4
5
6
7
class Product {

    String name;
    String category;

    // standard getters and setters
}

게다가 세부정보 개체에 대한 적절한 표현이 필요하다. 예를 들어 com.fasterxml.jackson.databind.JsonNode는 동적 키를 처리할 수 있다.

이를 사용하려면 Product 클래스에 필드로 추가해야 한다.

1
2
3
4
5
6
7
8
class Product {

    // common fields

    JsonNode details;

    // standard getters and setters
}

마지막으로 작동하는지 확인한다.

1
2
3
4
5
6
String json = "<json object>";

Product product = objectMapper.readValue(json, Product.class);

assertThat(product.getName()).isEqualTo("Pear yPhone 72");
assertThat(product.getDetails().get("audioConnector").asText()).isEqualTo("none");

그러나 이 솔루션에는 문제가 있다. 클래스는 JsonNode 필드가 있으므로 Jackson 라이브러리에 의존한다.

2. Map 사용

세부정보 필드에 java.util.Map을 사용하면 이 문제를 해결할 수 있다. 보다 정확하게는 Map<String, Object>를 사용해야 한다.

다른 모든 것은 동일하게 유지될 수 있다.

1
2
3
4
5
6
7
8
class Product {

    // common fields

    Map<String, Object> details;

    // standard getters and setters
}

그런 다음 테스트를 통해 이를 확인할 수 있다.

1
2
3
4
5
6
String json = "<json object>";

Product product = objectMapper.readValue(json, Product.class);

assertThat(product.getName()).isEqualTo("Pear yPhone 72");
assertThat(product.getDetails().get("audioConnector")).isEqualTo("none");

3. @JsonAnySetter 사용

이전 솔루션은 객체에 동적 속성만 포함된 경우 좋은 옵션이다. 그러나 때로는 단일 JSON 객체에 고정 속성과 동적 속성이 혼합되어 있는 경우도 있다.

예를 들어 제품 표현을 평면화해야 할 수도 있다.

1
2
3
4
5
6
{
    "name": "Pear yPhone 72",
    "category": "cellphone",
    "displayAspectRatio": "97:3",
    "audioConnector": "none"
}

이런 종류의 구조를 동적 객체로 처리할 수 있다. 불행하게도 이는 공통 속성을 정의할 수 없으며 동적으로 처리해야 함을 의미한다.

또는 @JsonAnySetter를 사용하여 알 수 없는 추가 속성을 처리하기 위한 메서드를 표시 할 수 있다. 이러한 메서드는 속성의 이름과 값이라는 두 가지 인수를 받아들여야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
class Product {

    // common fields

    Map<String, Object> details = new LinkedHashMap<>();

    @JsonAnySetter
    void setDetail(String key, Object value) {
        details.put(key, value);
    }

    // standard getters and setters
}

NullPointerExceptions를 방지하려면 세부정보 개체를 인스턴스화해야 한다.

동적 속성을 Map에 저장하므로 이전과 동일한 방식으로 사용할 수 있다.

1
2
3
4
5
6
String json = "<json object>";

Product product = objectMapper.readValue(json, Product.class);

assertThat(product.getName()).isEqualTo("Pear yPhone 72");
assertThat(product.getDetails().get("audioConnector")).isEqualTo("none");

[출처 및 참고]

This post is licensed under CC BY 4.0 by the author.