1. 개요

JSON으로 작업할 때의 일반적인 사용 사례는 한 모델에서 다른 모델로 변환을 수행하는 것입니다. 예를 들어 복잡하고 조밀하게 중첩된 개체 그래프를 다른 도메인에서 사용하기 위해 보다 간단한 모델로 구문 분석할 수 있습니다.

이 빠른 사용방법(예제)에서는 중첩된 값을 Jackson 과 매핑하여 복잡한 데이터 구조를 평면화하는 방법을 살펴보겠습니다 . 다음 세 가지 방법으로 JSON을 역직렬화합니다.

  • @JsonProperty 사용
  • JsonNode 사용
  • 커스텀 JsonDeserializer 사용

2. 메이븐 의존성

먼저 pom.xml 에 다음 의존성을 추가해 보겠습니다 .

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

Maven Central 에서 최신 버전의 jackson-databind를 찾을 수 있습니다 .

3. JSON 소스

예제의 소스 자료로 다음 JSON을 고려하십시오.

구조는 인위적이지만 두 수준 깊이로 중첩된 속성을 포함합니다.

{
    "id": "957c43f2-fa2e-42f9-bf75-6e3d5bb6960a",
    "name": "The Best Product",
    "brand": {
        "id": "9bcd817d-0141-42e6-8f04-e5aaab0980b6",
        "name": "ACME Products",
        "owner": {
            "id": "b21a80b1-0c09-4be3-9ebd-ea3653511c13",
            "name": "Ultimate Corp, Inc."
        }
    }  
}

4. 간소화된 도메인 모델

아래의 Product 클래스 에서 설명하는 평면화된 도메인 모델에서 소스 JSON 내에서 한 수준 깊이 중첩된 brandName 을 추출합니다 .

또한 중첩된 브랜드 개체 내에서 두 수준 깊이 중첩된 ownerName 을 추출합니다 .

public class Product {

    private String id;
    private String name;
    private String brandName;
    private String ownerName;

    // standard getters and setters
}

5. 어노테이션으로 매핑

중첩된 brandName 속성을 매핑하려면 먼저 중첩된 브랜드 개체를 Map 으로 압축 해제하고 name 속성을 추출 해야 합니다 . ownerName 을 매핑하기 위해 중첩된 소유자 개체를 Map 으로 압축 해제 하고 이름 속성을 추출합니다.

@JsonPropertyProduct 클래스 에 추가하는 몇 가지 사용자 지정 논리 의 조합을 사용하여 Jackson에게 중첩된 속성의 압축을 풀도록 지시할 수 있습니다 .

public class Product {
    // ...

    @SuppressWarnings("unchecked")
    @JsonProperty("brand")
    private void unpackNested(Map<String,Object> brand) {
        this.brandName = (String)brand.get("name");
        Map<String,String> owner = (Map<String,String>)brand.get("owner");
        this.ownerName = owner.get("name");
    }
}

클라이언트 코드는 이제 ObjectMapper를 사용하여 테스트 클래스 내에 문자열 상수 SOURCE_JSON 으로 존재하는 소스 JSON을 변환 할 수 있습니다.

@Test
public void whenUsingAnnotations_thenOk() throws IOException {
    Product product = new ObjectMapper()
      .readerFor(Product.class)
      .readValue(SOURCE_JSON);

    assertEquals(product.getName(), "The Best Product");
    assertEquals(product.getBrandName(), "ACME Products");
    assertEquals(product.getOwnerName(), "Ultimate Corp, Inc.");
}

6. JsonNode 로 매핑하기

중첩된 데이터 구조를 JsonNode 로 매핑하려면 약간의 추가 작업이 필요합니다.

여기서 ObjectMapperreadTree를 사용하여 원하는 필드를 구문 분석합니다.

@Test
public void whenUsingJsonNode_thenOk() throws IOException {
    JsonNode productNode = new ObjectMapper().readTree(SOURCE_JSON);

    Product product = new Product();
    product.setId(productNode.get("id").textValue());
    product.setName(productNode.get("name").textValue());
    product.setBrandName(productNode.get("brand")
      .get("name").textValue());
    product.setOwnerName(productNode.get("brand")
      .get("owner").get("name").textValue());

    assertEquals(product.getName(), "The Best Product");
    assertEquals(product.getBrandName(), "ACME Products");
    assertEquals(product.getOwnerName(), "Ultimate Corp, Inc.");
}

7. 사용자 지정 JsonDeserializer 로 매핑

중첩된 데이터 구조를 사용자 정의 JsonDeserializer 로 매핑하는 것은 구현 관점에서 JsonNode 접근 방식 과 동일합니다 .

먼저 JsonDeserializer를 만듭니다 .

public class ProductDeserializer extends StdDeserializer<Product> {

    public ProductDeserializer() {
        this(null);
    }

    public ProductDeserializer(Class<?> vc) {
        super(vc);
    }

    @Override
    public Product deserialize(JsonParser jp, DeserializationContext ctxt) 
      throws IOException, JsonProcessingException {
 
        JsonNode productNode = jp.getCodec().readTree(jp);
        Product product = new Product();
        product.setId(productNode.get("id").textValue());
        product.setName(productNode.get("name").textValue());
        product.setBrandName(productNode.get("brand")
          .get("name").textValue());
        product.setOwnerName(productNode.get("brand").get("owner")
          .get("name").textValue());		
        return product;
    }
}

7.1. Deserializer 수동 등록

사용자 지정 역직렬 변환기를 수동으로 등록하려면 클라이언트 코드에서 ModuleJsonDeserializer를 추가하고 Module을 ObjectMapper등록  하고 readValue를 호출해야 합니다 .

@Test
public void whenUsingDeserializerManuallyRegistered_thenOk()
 throws IOException {
 
    ObjectMapper mapper = new ObjectMapper();
    SimpleModule module = new SimpleModule();
    module.addDeserializer(Product.class, new ProductDeserializer());
    mapper.registerModule(module);

    Product product = mapper.readValue(SOURCE_JSON, Product.class);
 
    assertEquals(product.getName(), "The Best Product");
    assertEquals(product.getBrandName(), "ACME Products");
    assertEquals(product.getOwnerName(), "Ultimate Corp, Inc.");
}

7.2. 디시리얼라이저 자동 등록

JsonDeserializer 를 수동으로 등록하는 대신 클래스에 직접 deserializer를 등록 할 수 있습니다 .

@JsonDeserialize(using = ProductDeserializer.class)
public class Product {
    // ...
}

이 방법을 사용하면 수동으로 등록할 필요가 없습니다.

자동 등록을 사용하는 클라이언트 코드를 살펴보겠습니다.

@Test
public void whenUsingDeserializerAutoRegistered_thenOk()
  throws IOException {
 
    ObjectMapper mapper = new ObjectMapper();
    Product product = mapper.readValue(SOURCE_JSON, Product.class);

    assertEquals(product.getName(), "The Best Product");
    assertEquals(product.getBrandName(), "ACME Products");
    assertEquals(product.getOwnerName(), "Ultimate Corp, Inc.");
}

8. 결론

이 기사에서는 중첩된 값을 포함하는 JSON을 구문 분석하기 위해 Jackson을 사용하는 여러 가지 방법을 설명했습니다 . 더 많은 예제를 보려면 기본 Jackson Tutorial 페이지를 살펴보십시오 .

그리고 항상 그렇듯이 코드 스니펫은 GitHub 에서 찾을 수 있습니다 .

res – Jackson (eBook) (cat=Jackson)