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 으로 압축 해제 하고 이름 속성을 추출합니다.
@JsonProperty 와 Product 클래스 에 추가하는 몇 가지 사용자 지정 논리 의 조합을 사용하여 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 로 매핑하려면 약간의 추가 작업이 필요합니다.
여기서 ObjectMapper 의 readTree를 사용하여 원하는 필드를 구문 분석합니다.
@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 수동 등록
사용자 지정 역직렬 변환기를 수동으로 등록하려면 클라이언트 코드에서 Module 에 JsonDeserializer를 추가하고 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 에서 찾을 수 있습니다 .