목차
이번 글에서는 Json 파싱을 위한 라이브러리 중 하나인 jackson-databind에서 제공하는 ObjectMapper 객체를 사용하는 방법을 정리해보겠습니다. ObjectMapper 객체는 JSON 문자열을 파싱해서 Obejct로 변환하는 역할을 합니다. 대표적인 세 가지 JSON 유형으로, 단일 객체 유형, 배열에 객체가 포함된 유형, 단일 객체가 객체 배열을 포함하고 있는 유형이 있습니다. 이러한 유형의 문자열을 받아서 DTO로 변환하는 예시 코드를 통해 기본적인 ObjectMapper 사용법을 익히실 수 있기를 바랍니다.
Gradle dependencies
우선 필요한 라이브러리를 추가해야 합니다. gradle 기준으로 아래와 같이 jackson-databind 라이브러리를 추가합니다.
dependencies {
// https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind
implementation 'com.fasterxml.jackson.core:jackson-databind:2.16.1'
}
※ 만약 spring-boot-starter-web 라이브러리를 추가하면 그 안에 jackson-databind가 포함되어 있으므로 jackson-databind 라이브러리를 따로 추가하지 않아도 됩니다.
쉽게 볼 수 있는 JSON 유형
쉽게 볼 수 있는 JSON 유형을 아래와 같이 세 가지 유형으로 나누어 보았습니다.
- 단일 객체
{ "key" : "value" }
- 객체 배열
[ { "key1" : "value" }, {"key2" : "value"} ]
- 단일 객체가 객체 배열을 포함
{ "arrayKey" : [ { "key1" : "value" }, {"key2" : "value"} ]
,"key" : "value" }
각 유형 별로 ObjectMapper를 사용해 JSON을 DTO로 변환하는 테스트 코드를 작성했습니다.
테스트 코드
ObjectMapper
에게 필요한 정보를 주기 위해 DTO에 붙여 줄 두 가지 애너테이션과 ObjectMapper.readValue
메서드를 사용하여 JSON을 DTO로 변환합니다.
※ 참고로 ObjectMapper.writeValueAsString
메서드를 사용하면 DTO를 JSON 문자열로 변환할 수 있습니다.
Annotation
jackson-databind 라이브러리는 다양한 애너테이션을 제공합니다. ObjectMapper
객체가 JSON을 파싱해서 DTO 인스턴스를 만들 때 필요한 정보를 애너테이션을 통해 알려줘야 합니다. 이 글에서는 @JsonProperty
, @JsonIgrnoreProperties
애너테이션을 사용합니다.
@JsonProperty
key가 "name"
, "sirialNumber"
, "ipAddr"
등 일반적인 카멜 케이스인 경우는 굳이 명시해주지 않아도 됩니다. 하지만 "Name"
, "SerialNumber"
, "IPAddr"
등 일반적이지 않은 경우는 DTO의 멤버 변수에 @JsonProperty
애너테이션을 붙여주어야 합니다. 만약 key가 "DATA"
라면 @JsonProperty("DATA")
을 붙여서 key가 "DATA"
임을 명시합니다.
@JsonIgnoreProperties
JSON에 포함된 key, value 쌍이 DTO에 멤버 변수로 선언되어 있지 않을 때 어떻게 동작하게 할 것인지도 알려주어야 합니다. 아무 것도 명시하지 않으면 예외를 던집니다. 예외를 던지지 않고 무시하게 하려면 @JsonIgnoreProperties(ignoreUnknown = true)
을 명시합니다. ignoreUnknown = false
가 기본 값이기 때문에 모르는 프로퍼티가 있을 때 예외가 발생합니다.
DTO 클래스
JSON을 파싱하여 값을 세팅해줄 DTO 클래스입니다. name과 age를 멤버 변수로 가지고 있는 ResponseDto
클래스, 그리고 ResponseDto
객체 리스트를 감싸고 있는 WrapperResponseDto
클래스를 정의합니다.
@JsonIgnoreProperties(ignoreUnknown = true)
public class WrapperResponseDto {
@JsonProperty("DATA")
List<ResponseDto> data;
public List<ResponseDto> getData() {
return data;
}
@Override
public String toString() {
return "WrapperResponseDto{" +
"data=" + data +
'}';
}
}
public class ResponseDto {
private String name;
private int age;
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return "ResponseDto{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
즉, ObjectMapper
는 단일 객체 JSON을 ResponseDto
로 변환하고, 단일 객체가 객체 배열을 포함하는 경우의 JSON은 WrapperResponseDto
로 변환합니다.
Case 1: 단일 객체
@Test
void JSON_단일객체() throws JsonProcessingException {
String json = "{\"name\": \"greenneuron\"}";
ObjectMapper objectMapper = new ObjectMapper();
ResponseDto responseDto = objectMapper.readValue(json, ResponseDto.class);
System.out.println("responseDto = " + responseDto);
//출력 결과: responseDto = ResponseDto{name='greenneuron', age=0}
Assertions.assertThat(responseDto.getName()).isEqualTo("greenneuron");
}
단일 객체를 나타내는 JSON이 ResponseDto
으로 변환되었습니다.
Case 2: 객체 배열
@Test
void JSON_객체배열() throws JsonProcessingException {
String json = "[ {\"name\": \"greenneuron\"}, {\"name\": \"foo\"} ]";
ObjectMapper objectMapper = new ObjectMapper();
ResponseDto[] responseDtos = objectMapper.readValue(json, ResponseDto[].class);
System.out.println("responseDtos = " + Arrays.toString(responseDtos));
//출력 결과: responseDtos = [ResponseDto{name='greenneuron', age=0}, ResponseDto{name='foo', age=0}]
Assertions.assertThat(responseDtos).extracting("name")
.containsExactly("greenneuron", "foo");
}
객체 배열을 나타내는 JSON이 ResponseDto
배열로 변환되었습니다.
Case 3: 단일 객체가 객체 배열을 포함
@Test
void JSON_단일객체가_객체배열을_포함() throws JsonProcessingException {
String json = "{ \"DATA\": [{\"name\": \"greenneuron\", \"age\": 20}, {\"name\": \"foo\", \"age\": 25}], \"DATA2\":\"\"}";
ObjectMapper objectMapper = new ObjectMapper();
WrapperResponseDto wrapperResponseDto = objectMapper.readValue(json, WrapperResponseDto.class);
System.out.println("wrapperResponseDto = " + wrapperDto);
//출력 결과: wrapperResponseDto = WrapperResponseDto{data=[ResponseDto{name='greenneuron', age=20}, ResponseDto{name='foo', age=25}]}
Assertions.assertThat(wrapperResponseDto.getData()).extracting("name")
.containsExactly("greenneuron", "foo");
Assertions.assertThat(wrapperResponseDto.getData()).extracting("age").containsExactly(20, 25);
}
JSON에서 객체 배열을 나타내는 DATA가 ResponseDto
배열로 변환되었으며, 변환된 값이 WrapperResponseDt
o의 data
멤버 변수로 세팅 되었습니다. @JsonIgnoreProperties(ignoreUnknown = true)
에 의해서 “DATA2″는 무시됩니다.
참고 자료
- https://github.com/FasterXML/jackson-databind
- https://github.com/FasterXML/jackson-docs