[번역] Intro to the Jackson ObjectMapper + 예제 코드

rin·2020년 7월 14일
3
post-thumbnail

ref. https://www.baeldung.com/jackson-object-mapper-tutorial
예제 코드는 github에서 확인 할 수 있습니다.

Overview

이 글은 Jackson의 ObjectMapper 클래스를 이해하는 것과 자바 오브젝트를 JSON으로 시리얼라이징하거나 JSON 문자열을 자바 오브젝트로 디시리얼라이징 하는 방법에 집중하여 작성되었다. 일반적인 Jackson 라이브러리에 대해 더욱 이해하고 싶다면 Jackson Tutorial이 시작하기에 알맞을 것이다.

Dependencies

우선 (maven인 경우) pom.xml에 다음과 같이 종속성을 추가한다.

maven :

<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.11.0</version>
</dependency>

gradle:

// https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind
compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.11.0'

이 종속성은 또한 다음의 라이브러리를 클래스패스에 자체적으로 추가시킨다.

  1. jackson-annotstions-2.11.0.jar
  2. jackson-core-2.11.0.jar
  3. jackson-databind-2.11.0.jar

Maven 중앙 레파지토리에서 Jackson databind에 대한 가장 최신 버전을 언제든지 사용할 수 있다.

Reading and Writing Using ObjectMapper

기본적인 읽기 및 쓰기 연산을 시작해보자!

간단한 ObjectMapper의 readValue API는 좋은 엔트리포인트(진입점)이다. 이것을 JSON 컨텐츠를 Java 오브젝트로 변경하는 파싱이나 디시리얼라이징에 사용할 수 있다. 또한, 쓰는 것에 있어서는 writeValue API 를 어떤 Java 오브젝트를 JSON 아웃풋으로 시리얼라이징하는데에 사용할 수 있다.

이 글 전체에 걸쳐서 두 개의 필드를 가지고 있는 다음의 Car class를 오브젝트로 사용하여 시리얼라이즈와 디시리얼라이즈를 수행할 것이다.

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Car {
    private String color;
    private String type;
}

필자는 spring boot + gradle로 예제를 진행하였다.

lombok을 추가해주자 🙋🏻

compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'

Java Object to JSON

ObjectMapper 클래스의 writeValue 메소드를 사용하여 Java 오브젝트를 JSON으로 시리얼라이징 하는 예제이다.

@SpringBootTest
class ObjectmapperApplicationTests {

	@Test
	void test1() throws IOException {
		ObjectMapper objectMapper = new ObjectMapper();
		Car car = new Car("yellow", "renault");
		objectMapper.writeValue(new File("target/car.json"), car);
	}

}

테스트를 실행하기 전에 현재 workspace에서 $> mkdir target 명령어를 이용해 target이라는 폴더를 생성해주자.

테스트를 수행한 뒤 $> cat target/car.json 을 입력하면 다음과 같이 JSON 파일로 저장된 것을 확인 할 수 있을 것이다.

ObjectMapper 클래스의 writeValueAsString, writeValueAsBytes는 Java 오브젝트로 부터 JSON을 만들고 이를 문자열 혹은 Byte 배열로 반환한다.

✔️ writeValueAsString

	@Test
	void test2() throws IOException {
		ObjectMapper objectMapper = new ObjectMapper();
		Car car = new Car("yellow", "renault");
		String result = objectMapper.writeValueAsString(car);
		System.out.println(" >>>>>> "+result);
	}

✔️ writeValueAsBytes

	@Test
	void test3() throws IOException {
		ObjectMapper objectMapper = new ObjectMapper();
		Car car = new Car("yellow", "renault");
		byte[] result = objectMapper.writeValueAsBytes(car);
		System.out.println(" byte[] >>>>>> "+result);
		System.out.println(" toString >>>>>> "+ Arrays.toString(result));
	}

JSON to Java Object

다음은 ObjectMapper 클래스를 사용하여 JSON 문자열을 Java 오브젝트로 변환하는 간단한 예제이다.

	@Test
	@DisplayName("json string to java object")
	void test4() throws JsonProcessingException {
		ObjectMapper objectMapper = new ObjectMapper();
		String json = "{ \"color\" : \""+COLOR+"\", \"type\" : \""+TYPE+"\" }";
		Car car = objectMapper.readValue(json, Car.class);

		assertThat(car.getColor(), equalTo(COLOR));
		assertThat(car.getType(), equalTo(TYPE));
	}

readValue() 함수는 JSON 문자열을 포함하는 파일과 같은 다른 형태의 인풋도 허용한다.

✔️ new File
우선 test 패키지와 동일한 depth에 resources 폴더를 만들고 하위에 json_car.json 파일을 생성한다.

{
  "color" : "Black",
  "type" : "BMW"
}

테스트 코드는 다음과 같다.

@Test
@DisplayName("json string file to java object")
void test5() throws IOException {
	ObjectMapper objectMapper = new ObjectMapper();
	Car car = objectMapper.readValue(new File("src/test/resources/json_car.json"), Car.class);

	assertThat(car.getColor(), equalTo(COLOR));
	assertThat(car.getType(), equalTo(TYPE));
}

✔️ new URL

@Test
@DisplayName("json string file-url to java object")
void test6() throws IOException {
	ObjectMapper objectMapper = new ObjectMapper();
	Car car = objectMapper.readValue(new URL("file:src/test/resources/json_car.json"), Car.class);

	assertThat(car.getColor(), equalTo(COLOR));
	assertThat(car.getType(), equalTo(TYPE));
}

JSON to Jackson JsonNode

또한 JSON을 JsonNode 오브젝트로 파싱한 뒤 특정한 노드로부터 데이터를 회수하여 사용할 수 있다.

	@Test
	@DisplayName("json to jackson JsonNode")
	void test7() throws IOException {
		ObjectMapper objectMapper = new ObjectMapper();
		String json = "{ \"color\" : \""+COLOR+"\", \"type\" : \""+TYPE+"\" }";
		JsonNode jsonNode = objectMapper.readTree(json);
		String color = jsonNode.get("color").asText();
		String type = jsonNode.get("type").asText();

		assertThat(color, equalTo(COLOR));
		assertThat(type, equalTo(TYPE));
	}

Creating a Java List from a JSON Array String

TypeReference를 사용해 배열 형식 내부의 JSON을 Java 오브젝트 리스트로 파싱할 수 있다.


static final String[] COLORS = {"Black", "Red"};
static final String[] TYPES = {"BMW", "FIAT"};

@Test
@DisplayName("JSON array string to java list")
void test8() throws JsonProcessingException {
	String jsonCarArray = getJsonCarArray();

	ObjectMapper objectMapper = new ObjectMapper();
	List<Car> cars = objectMapper.readValue(jsonCarArray, new TypeReference<List<Car>>() {});

	String converted = objectMapper.writeValueAsString(cars).replaceAll(" ","");
	assertThat(converted, equalTo(jsonCarArray.replaceAll(" ","")));
}

private String getJsonCarArray() {
	String jsonCarArray = "[";
	for (int i = 0; i < COLORS.length; ++i){
		if(i != 0) {
			jsonCarArray += ", ";
		}
		jsonCarArray += "{ \"color\" : \"";
		jsonCarArray += COLORS[i];
		jsonCarArray += "\", \"type\" : \"";
		jsonCarArray += TYPES[i];
		jsonCarArray += "\" }";
	}
	jsonCarArray += "]";
}

Creating Java Map from JSON String

유사하게, JSON을 Java Map으로 변경할 수 있다.

	@Test
	@DisplayName("JSON array string to java map")
	void test9() throws JsonProcessingException {
		String json = "{ \"color\" : \""+COLOR+"\", \"type\" : \""+TYPE+"\" }";

		ObjectMapper objectMapper = new ObjectMapper();
		Map<String, Object> cars = objectMapper.readValue(json, new TypeReference<Map<String, Object>>() {});

		String converted = objectMapper.writeValueAsString(cars).replaceAll(" ","");
		assertThat(converted, equalTo(json.replaceAll(" ","")));
	}

Advanced Features

Jackson 라이브러리의 가장 강력한 기능 중 하나는 뛰어난 사용자 지정 시리얼라이징/디시리얼라이징 처리이다.

이 섹션에서는 입력 또는 출력 JSON 응답이 응답을 생성하거나 소비하는 객체와 다른 몇 가지 고급 기능을 살펴볼 것이다.

Configuring Serialization or Deserialization Feature

JSON 오브젝트를 Java 클래스로 컨버팅할 때, JSON 문자열이 몇개의 새로운 필드를 가지는 경우 기본적으로 처리 과정에서 예외 처리 된다.

String jsonString 
  = "{ \"color\" : \"Black\", \"type\" : \"Fiat\", \"year\" : \"1970\" }";

위의 예제에서 JSON 문자열이 Car 클래스로의 Java 오브젝트 파싱과정에서 기본적으로 UnrecognizedPropertyException 예외가 발생될 것이다.

configure 메소드를 통해 새로운 필드를 제외시키도록 기본 처리를 확장할 수 있다.

@Test
@DisplayName("ignore new field")
void test10() throws JsonProcessingException {
	String jsonString = "{ \"color\" : \""+COLOR+"\", \"type\" : \""+TYPE+"\", \"year\" : \""+ YEAR +"\" }";

	ObjectMapper objectMapper = new ObjectMapper();
	objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
	Car car = objectMapper.readValue(jsonString, Car.class);

	JsonNode jsonNodeRoot = objectMapper.readTree(jsonString);
	JsonNode jsonNodeYear = jsonNodeRoot.get("year");
	String year = jsonNodeYear.asText();

	assertThat(year, equalTo(YEAR));
	assertThat(car.getColor(), equalTo(COLOR));
	assertThat(car.getType(), equalTo(TYPE));
}

반면에 다른 옵션인 FAIL_ON_NULL_FOR_PRIMITIVES 은 초기값이 null인 경우에 다음처럼 정의할 수 있다.

objectMapper.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, false);

유사하게 FAIL_ON_NUMBERS_FOR_ENUM 는 enum 값을 숫자로 시리얼라이징/디시리얼라이징 하는 경우를 컨트롤 할 수 있다.

objectMapper.configure(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS, false);

시리얼라이징과 디시리얼라이징 기능에 대해 종합적인 리스트를 찾고싶다명 공식 사이트를 확인하라.

Creating Custom Serializer or Deserializer

ObjectMApper 클래스의 다른 효과적인 기능은 사용자 지정 시리얼라이저와 디시리얼라이저를 등록할 수 있는 것이다. 커스텀 시리얼라이저/디시리얼라이저는 인풋 혹은 아웃풋 JSON 결과가 Java 클래스와 비교했을때 형태가 다른 경우를 시리얼라이징/디시리얼라이징 할 때 매우 유용하다.

다음은 커스텀 JSON 시리얼라이저 예제이다 :

public class CarSerializer extends StdSerializer<Car> {

    public CarSerializer() {
        this(null);
    }

    public CarSerializer(Class<Car> t) {
        super(t);
    }

    @Override
    public void serialize(Car car, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        jsonGenerator.writeStartObject();
        jsonGenerator.writeStringField("car_brand", car.getType());
        jsonGenerator.writeEndObject();
    }
}

테스트 코드는 새로운 테스트 클래스에서 작성하였다.

@SpringBootTest
class CarSerializerTest {

    final static String COLOR = "YELLOW";
    final static String TYPE = "RENAULT";

    @Test
    @DisplayName("custom serializer인 CarSerializer를 사용하여 특정 필드만 다른 이름의 key로 변환한다.")
    void test1() throws JsonProcessingException {
        ObjectMapper mapper = new ObjectMapper();
        SimpleModule module = new SimpleModule("CarSerializer", new Version(1, 0, 0, null, null, null));
        module.addSerializer(Car.class, new CarSerializer());
        mapper.registerModule(module);
        Car car = new Car(COLOR, TYPE);

        String carJson = mapper.writeValueAsString(car);
        System.out.println(carJson);
    }

}

수행 결과는 다음과 같다. (serializer에 의해 변환된 값)

커스텀 JSON 디시리얼라이저의 예제는 이것이다 :

public class CarDeserializer extends StdDeserializer<Car> {

    public CarDeserializer() {
        this(null);
    }

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

    @Override
    public Car deserialize(JsonParser parser, DeserializationContext deserializer) throws IOException, JsonProcessingException {
        Car car = new Car();
        ObjectCodec codec = parser.getCodec();
        JsonNode node = codec.readTree(parser);

        JsonNode colorNode = node.get("color");
        String color = colorNode.asText();
        car.setColor(color);

        return car;
    }
}

이 커스텀 디시리얼라이저는 다음 방식으로 적용될 수 있다.

@SpringBootTest
class CarDeserializerTest {

    final static String COLOR = "YELLOW";
    final static String TYPE = "RENAULT";

    @Test
    @DisplayName("")
    void test1() throws JsonProcessingException {
        String json =  "{ \"color\" : \""+COLOR+"\", \"type\" : \""+TYPE+"\" }";
        ObjectMapper mapper = new ObjectMapper();
        SimpleModule module = new SimpleModule("CustomCarDeserializer", new Version(1, 0, 0, null, null, null));
        module.addDeserializer(Car.class, new CarDeserializer());
        mapper.registerModule(module);
        Car car = mapper.readValue(json, Car.class);

        assertThat(car.getColor(), equalTo(COLOR));
        assertThat(Optional.ofNullable(car.getType()).isPresent(), equalTo(false));
    }
}

Handling Date Formats

기본적인 java.util.Date 시리얼라이징은 epoch timestamp(number of milliseconds since January 1st, 1970, UTC)로 이뤄진다. 그러나 이는 인간이 읽기에 친숙하지 않으며, 사람이 읽어내는 포맷 내에서 보여주기에 더 나은 형식이 요구된다.

지금까지 사용해온 Car 인스턴스를 datePurched 속성과 함께 Request 클래스로 래핑하자.

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Request {
    
    private Car car;
    private Date datePurchased;
    
}

date를 문자열 형식으로 다루기 위해 이를 다음의 코드를 고려하며 yyyy-MM-dd HH:mm과 같은 상태로 셋팅한다.

	@Test
	@DisplayName("Date 타입을 포함하는 Request 오브젝트를 JSON 문자열로 출력한다.")
	void test11() throws JsonProcessingException {
		ObjectMapper objectMapper = new ObjectMapper();
		DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm a z");
		objectMapper.setDateFormat(df);

		Car car = new Car(COLOR, TYPE);
		Request request = new Request(car, Date.valueOf(LocalDate.now()));

		String carAsString = objectMapper.writeValueAsString(request);
		System.out.println(carAsString);
	}

Jackson을 이용하는 날짜의 시리얼라이징에 대해 더 공부하고 싶으면 여기를 참고하라.

Handling Collections

큰 것은 아니지만 DeserializationFeature 클래스를 통해 유용하게 사용가능한 기능으로 JSON 배열을 컬렉션 타입으로 변환할 수 있다.

예를 들어, 다음처럼 결과를 배열로 생성할 수 있다.

	@Test
	@DisplayName("Json array를 Object array로 변경한다.")
	void test12() throws JsonProcessingException {
		String jsonCarArray = getJsonCarArray();

		ObjectMapper objectMapper = new ObjectMapper();
		objectMapper.configure(DeserializationFeature.USE_JAVA_ARRAY_FOR_JSON_ARRAY, true);
		Car[] cars = objectMapper.readValue(jsonCarArray, Car[].class);

		String converted = objectMapper.writeValueAsString(cars).replaceAll(" ","");
		assertThat(converted, equalTo(jsonCarArray.replaceAll(" ","")));
	}

혹은 리스트로도 가능하다

	@Test
	@DisplayName("JSON array string to java list")
	void test8() throws JsonProcessingException {
		String jsonCarArray = getJsonCarArray();

		ObjectMapper objectMapper = new ObjectMapper();
		List<Car> cars = objectMapper.readValue(jsonCarArray, new TypeReference<List<Car>>() {});

		String converted = objectMapper.writeValueAsString(cars).replaceAll(" ","");
		assertThat(converted, equalTo(jsonCarArray.replaceAll(" ","")));
	}

Jackson과 함께 컬렉션을 핸들링 하는 것과 관련된 정보는 여기에서 얻을 수 있다.

Conclusion

Jackson은 Java를 위한 강력한 JSON 시리얼라이징/디시리얼라이징 라이브러리이다. ObjectMapper API는 유연성이 뛰어난 JSON 응답 객체의 구문을 분석하고 생성하는 간단한 방법을 제공한다.

이 글에서는 라이브러리를 인기있게 만든 주요 기능에 대해 설명한다. 이 글과 함께 제공되는 소스 코드는 GitHub에서 찾을 수 있다.

위에서 사용된 코드는 해당 글을 참조하며 자체적으로 작성한 코드이며 개인 레파지토리 github에서 확인 할 수 있습니다.

profile
🌱 😈💻 🌱

2개의 댓글

comment-user-thumbnail
2021년 3월 31일

이야 ㅋㅋ 필요한 부분 잘 배워갑니다

답글 달기
comment-user-thumbnail
2021년 7월 3일

좋은 글 감사합니다~

답글 달기