티스토리 뷰
모든 소스는 여기서 확인하실 수 있습니다.
Optional을 Jackson을 이용해 Serialize/Deserialize하면 어떻게 될까요?
Optional 필드를 가지는 Object
하나의 Optional 필드를 가지는 Name이라는 클래스를 생성하였습니다.
package io.lcalmsky.jackson.domain;
import lombok.AccessLevel;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Optional;
public class BeanWithOptional {
@Data
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public static class Name {
private String firstName;
private String lastName;
private Optional<String> nickname;
}
}
Optional을 필드변수로 사용하는 것은 권장하는 사항이 아니라 단순 테스트를 위함입니다.
직렬화(Serialization)
package io.lcalmsky.jackson.domain;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import java.util.Optional;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
public class BeanWithOptionalTests {
@Test
public void givenBeanWithOptional_whenSerialize_thenCorrect() throws JsonProcessingException {
// given
BeanWithOptional.Name name = new BeanWithOptional.Name();
name.setFirstName("Lionel");
name.setLastName("Messi");
name.setNickname(Optional.of("GOAT"));
// when
String json = new ObjectMapper().writeValueAsString(name);
// then
assertThat(json, not(containsString("GOAT")));
System.out.println(json);
}
}
{"firstName":"Lionel","lastName":"Messi","nickname":{"present":true}}
Process finished with exit code 0
Optional 필드 출력시 값이 포함되지 않고 present라는 필드가있는 중첩 JSON 객체가 포함되어 있음을 확인할 수 있습니다.
그 이유는 Optional 클래스의 public getter 메소드가 isPresent이기 때문입니다. 따라서 직렬화시에 nickname 필드에 값이 있으면 true, 없으면 false가 present라는 필드에 저장됩니다.
다시 아래와 같이 테스트를 해보면,
package io.lcalmsky.jackson.domain;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class BeanWithOptionalTests {
private String json;
@Test
@DisplayName("Optional 필드를 직렬화하면 값이 포함되지 않음")
public void test1() throws JsonProcessingException {
// given
BeanWithOptional.Name name = givenName("GOAT");
// when
json = whenSerialize(name);
// then
assertFalse(() -> json.contains("GOAT"));
}
@Test
@DisplayName("Optional 필드를 직렬화하면 isPresent의 값이 포함됨")
public void test2() throws JsonProcessingException {
// given
BeanWithOptional.Name name = givenName("GOAT");
// when
json = whenSerialize(name);
// then
assertTrue(() -> json.contains("true"));
}
@Test
@DisplayName("Optional 필드를 null로 지정하면 isPresent값이 false로 지정됨")
public void test3() throws JsonProcessingException {
// given
BeanWithOptional.Name name = givenName(null);
// when
json = whenSerialize(name);
// then
assertTrue(() -> json.contains("false"));
}
private String whenSerialize(BeanWithOptional.Name name) throws JsonProcessingException {
return new ObjectMapper().writeValueAsString(name);
}
private BeanWithOptional.Name givenName(String nickname) {
BeanWithOptional.Name name = new BeanWithOptional.Name();
name.setFirstName("Lionel");
name.setLastName("Messi");
name.setNickname(Optional.ofNullable(nickname));
return name;
}
@AfterEach
public void teardown() {
System.out.println(json);
}
}
원하는 테스트 결과를 얻을 수 있습니다.
역직렬화(Deserialization)
반대로 실제 값을 대입하여 역직렬화를 하였을 때는 JsonMappingException이 발생합니다.
@Test
@DisplayName("Optional 필드에 값이 포함된 JSON을 역직렬화하면 예외가 발생함")
public void givenJsonWithRealValue_whenDeserialize_thenThrowsJsonMappingException() throws JsonProcessingException {
// given
json = "{\"firstName\":\"Lionel\",\"lastName\":\"Messi\",\"nickname\":\"GOAT\"}";
// when & then
assertThrows(
JsonMappingException.class,
() -> new ObjectMapper().readValue(json, BeanWithOptional.Name.class)
);
}
본질적으로 Jackson은 nickname 값을 인수로 사용하는 생성자가 필요하지만 Optional 필드는 해당되지 않습니다.
Optional 필드가 포함된 클래스를 역직렬화 하기 위해선 @JsonSetter 어노테이션 등을 이용하여 역직렬화시 사용할 메서드를 지정해주시면 됩니다.
@Data
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public static class NameWithOptionalSetter {
private String firstName;
private String lastName;
private Optional<String> nickname;
@JsonSetter
public void setNickname(String nickname) {
this.nickname = Optional.ofNullable(nickname);
}
}
@Test
@DisplayName("Optional 필드에 값이 포함된 JSON을 setter를 지정하여 역직렬화하면 성공함")
public void givenJsonWithRealValueAndSetter_whenDeserialize_thenSuccess() throws JsonProcessingException {
// given
json = "{\"firstName\":\"Lionel\",\"lastName\":\"Messi\",\"nickname\":\"GOAT\"}";
// when
BeanWithOptional.NameWithOptionalSetter name = new ObjectMapper().readValue(json, BeanWithOptional.NameWithOptionalSetter.class);
// then
assertTrue(() -> name.getNickname().filter("GOAT"::equals).isPresent());
System.out.println(name);
}
'Java' 카테고리의 다른 글
URL을 이용하여 파일 다운 받기 (0) | 2020.06.17 |
---|---|
Map.toString()을 다시 Map으로 역직렬화 할 수 있을까? (0) | 2020.04.22 |
Jackson의 모든 것 - 커스터마이징 편 (0) | 2019.11.26 |
Jackson의 모든 것 - 일반편 (0) | 2019.11.22 |
Jackson의 모든 것 - 다형성 편 (0) | 2019.11.21 |
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
링크
TAG
- @ManyToOne
- 스프링 부트
- Linux
- Jackson
- JPA
- 스프링 부트 애플리케이션
- proto3
- r
- Spring Boot JPA
- 알고리즘
- 함께 자라기 후기
- 스프링 부트 회원 가입
- 스프링 부트 튜토리얼
- leetcode
- 스프링 데이터 jpa
- spring boot jwt
- Spring Boot
- 클린 아키텍처
- 스프링부트
- spring boot application
- Spring Boot Tutorial
- JSON
- Java
- QueryDSL
- 함께 자라기
- 헥사고날 아키텍처
- intellij
- gRPC
- Spring Data JPA
- spring boot app
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
글 보관함