ObjectMapper 사용법

오늘은 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 배열로 변환되었으며, 변환된 값이 WrapperResponseDto의 data 멤버 변수로 세팅 되었습니다. @JsonIgnoreProperties(ignoreUnknown = true) 에 의해서 “DATA2″는 무시됩니다.

참고 자료

  • https://github.com/FasterXML/jackson-databind
  • https://github.com/FasterXML/jackson-docs
스스로 경험하며 얻은 깨달음을 공유하기 좋아하며, 세상이 필요로 하는 코드를 작성하기 위해 노력하는 개발자입니다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다