Jackson | boolean 타입의 isA 멤버의 직렬화 문제

주싱·2022년 4월 5일
0

Java

목록 보기
5/6

문제

Java 프로젝트에서 boolean 타입의 isUp 이라는 객체 멤버가 Jackson 라이브러리를 통해 JSON 문자열로 직렬화되면 필드 이름이 up 로 바뀌는 문제가 발생합니다. 직렬화된 JSON 문자열을 사용하는 측에서 isUp 이라는 고정된 이름을 사용하고 있고, 상대측 코드를 수정할 수 없는 상황이라서 문제가 되었습니다.

@Test
void test() throws JsonProcessingException {
    ObjectMapper mapper = new ObjectMapper();
    Message message = new Message(true);
    String serialized = mapper.writeValueAsString(message);
    System.out.println("serialized = " + serialized);
}

@Getter
static class Message {
    private final boolean isUp;

    public Message(boolean isUp) {
        this.isUp = isUp;
    }
}
serialized = {"up":true}

가설

Jackson 라이브러리 ObjectMapper 가 객체를 JSON 문자열로 직렬화할 때에 객체의 Getter 이름을 보고 필드 이름을 결정한다.

(※ 사실 처음부터 이렇게 가설을 설정한 것은 아닙니다. 이것저것 해보다가 알게 되었는데 글의 흐름이 읽기 좋게 하기 위해 가설이라고 먼저 설정합니다.)

검증

isUp()

먼저 문제가 발생한 Getter 이름이 isUp() 인 경우입니다. Lombok @Getter 어노테이션을 사용하거나 직접 Getter 를 isUp() 라고 정의해 주는 경우입니다. 위 문제에서 본 것과 같이 직렬화된 필드 이름이 up 이 됩니다.

getIsUp()

일반적인 getter 이름법은 아니지만 이렇게 정의하는 경우 필드 이름이 isUp 으로 직렬화 됩니다.

@Test
void test() throws JsonProcessingException {
    ObjectMapper mapper = new ObjectMapper();
    Message message = new Message(true);
    String serialized = mapper.writeValueAsString(message);
    System.out.println("serialized = " + serialized);
}

static class Message {
    private final boolean isUp;

    public Message(boolean isUp) {
        this.isUp = isUp;
    }
	
		public boolean getIsUp() {
				return isUp;
		}
}
serialized = {"isUp":true}

해결책

ObjectMapper.setVisibility()

첫번째 해결책은 Jackson 라이브러리 ObjectMappersetVisibility() 설정을 사용하는 것입니다. 아래와 같이 setVisibility 설정을 사용하면 Getter 없이도 객체 필드 이름이 그대로 직렬화되는 것을 확인할 수 있습니다.

@Test
void test() throws JsonProcessingException {
    ObjectMapper mapper = new ObjectMapper();
    Message message = new Message(true);
    mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
    String serialized = mapper.writeValueAsString(message);
    System.out.println("serialized = " + serialized);
}

static class Message {
    private final boolean isUp;

    public Message(boolean isUp) {
        this.isUp = isUp;
    }
}
serialized = {"isUp":true}

getIsUp()

두번째 해결책은 위에서 살펴본 것 처럼 일반적인 이름은 아니지만 getIsUp() 이라는 Getter 를 직접 정의해 주는 것입니다. 사실 첫 번째 해결책이 더 나은 것 같습니다.

결론

저의 경우에는 Stomp 프로토콜의 Publisher 코드를 작성하기 위해 SimpMessagingTemplate.convertAndSend() 메서드를 사용하고 있었는데요. 이런 경우 ObjectMapper 를 직접 다루지 않고 라이브러리 내부에서 변환이 일어나기 때문에 setVisibility() 설정을 사용할 수 없었습니다. 그래서 두 번째 해결책인 값 객체를 정의할 때 Lombok 의 @Getter 어노테이션을 제거하고 getIsUp() 과 같이 Getter 를 직접 정의해 주는 방법으로 문제를 해결하였습니다.

profile
소프트웨어 엔지니어, 일상

2개의 댓글

comment-user-thumbnail
2022년 12월 26일

원시타입의 boolean 대신 Boolean Wrapper 클래스를 사용하게 되면 필드명 그대로 처리할 수 있습니다.

1개의 답글