ObjectMapper Deserialize 커스텀 하기

GyuHyeong·2022년 2월 20일
1
post-thumbnail

자바 진영에서 개발을 하다보면 Jackson을 이용해서 JSON형식의 데이터를 역직렬화 하는 경우가 많습니다.

Jackson을 이용한다면 ObjectMapper를 사용하게 되는데,
ObjectMapper는 기본적으로 클래스에 선언된 Setter/Getter메소드를 참조해 데이터를 직렬화 하고 있습니다.
(* Gson은 클래스 필드를 참조합니다.)

또한 ObjectMapper는 Json텍스트를 자바의 객체로 변환할때 자바에서 익히 많이 쓰이는 java.lang.* / java.util.* 등의 패키지 클래스로 변환합니다.

만약 JSON데이터가 ObjectMapper의 기본 역직렬화 참조 클래스가 아닌, 다른 개인이 만든 클래스로 역직렬화를 시키려면 어떻게 해야할까요?

바로 ObjectMapper에 모듈을 추가해주면 됩니다.

1. 변환할 객체 정의하기

ObjectMapper를 이용해 역직렬화 할 대상 클래스를 정의합니다.

우선 User라는 클래스를 정의하겠습니다.
잘 보시면 Update라는 객체 타입의 멤버 변수가 있습니다.
Update 클래스는 Spring Data MongoDB에서 사용하는 클래스입니다.

이 객체는 java.lang / java.util 패키지엔 없는 클래스이고, Update Class자체를 직렬화해서 만든 JSON데이터를 역직렬화하면 역직렬화가 되지 않는 클래스입니다.

해당 내용에 대해선 다른 포스트에서 좀 더 자세하게 다뤄보도록하고, 우선 ObjectMapper를 어떻게 잘 구슬려볼지에 포커스를 맞추겠습니다.

2. ObjectMapper가 특정 클래스를 역직렬화 할때 참조할 모듈 생성하기

public class CustomDeserializer extends StdDeserializer<User> {
    public CustomDeserializer() {
        this(null);
    }

    protected CustomDeserializer(Class<?> vc) {
        super(vc);
    }

    @Override
    public User deserialize(JsonParser parser, DeserializationContext deserializer) throws IOException {
        ObjectCodec codec = parser.getCodec();
        JsonNode treeNode = codec.readTree(parser);
        User user = new User();
        try {
            for(Field field : User.class.getDeclaredFields()){
                JsonNode fieldNode = treeNode.get(field.getName());
                if(fieldNode == null) continue;
                field.setAccessible(true);
                // field가 null인 경우 x
                if(!field.getName().equals(Key.UPDATE_OBJECT)) {
                    if(field.getName().contains("Id")) {
                        long idValue = fieldNode.asLong();
                        field.set(user, idValue);
                    }else{
                        String stringValue = fieldNode.asText();
                        field.set(user, stringValue);
                    }
                }
                else{
                    field.set(user, Update.fromDocument(Document.parse(fieldNode.toString())));
                }
            }
        } catch (IllegalAccessException e) {
            throw new CustomException(ErrorCode.GENERIC_INSTANTIATING_FAILED);
        }
        return user;
    }
}

저는 java의 reflection을 이용해 매핑했지만, 클래스 하나만의 매핑을 위한 설정이라면, setter를 사용하시는것도 방법입니다.

try/catch 부분의 try부분을 잘 보시면, if에서 field의 이름을 보고 구분해서 데이터를 매핑해서 반환하고 있습니다.

필드명이 update가 아닌 필드는 일반 데이터 필드이고,
~~Id가 포함된 필드 이름이라면 해당 클래스의 필드에선 Long타입으로 필드를 선언되어있기때문에, asLong()으로 데이터를 반환받아 매핑하고, Id가 포함되지 않은 필드 명을 가진 필드들은 String 타입이기 때문에 asText()로 반환받아서 세팅합니다.

만약 필드명이 update인 경우엔 Update 클래스로 변환해야하므로
org.bson 패키지의 Document클래스의 parse(String json)을 이용하여 asText()로 반환받는게 아닌, byte[] 그 자체로 Document 클래스로 역직렬화 한 뒤 그 Document를 Update객체로 파싱하는 과정을 거치면 됩니다.

이 부분에 대해선 다음에 MongoDB + Spring for Kafka 부분을 다룰때 좀 더 자세하게 설명하겠습니다.

아무튼 이렇게 ObjectMapper의 모듈 클래스를 만드셨다면

3. ObjectMapper를 반환할 클래스,메소드 생성하기

public class CustomUserDeserialize{

    public static ObjectMapper customUserMapper(){
                ObjectMapper objectMapper = JsonMapper.builder()
                .configure(MapperFeature.DEFAULT_VIEW_INCLUSION, false)
                .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
                .build();
        SimpleModule module = new SimpleModule("CustomDeserializer", new Version(1, 0, 0, null, null, null));
        module.addDeserializer(User.class, new CustomDeserializer());
        objectMapper.registerModule(module);
        return objectMapper;
    }

}

MapperFeature.DEFAULT_VIEW_INCLUSION - Serialize시 @JsonView 애너테이션이 설정된 필드만 노출시키는 옵션입니다.
해당 옵션은 Serialize시 영향이 미치는 옵션이기때문에 선언 안해도 상관없습니다.

DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES - Json에는 Key:Value 데이터가 있는데, 변환할 Class에는 해당 Key값을 가진 Field가 없을때 해당 key값을 건너뛸지, 말지에 대한 설정입니다. true는 건너뛰지않고 예외가 발생하고, false는 건너뜁니다.

4. 사용하기

해당 내용은 spring for kafka를 이용해 테스트 하는 과정이므로 이점 참고하시기 바랍니다.

보낸 데이터

{
    "mobile":"010-1234-1234",
    "nickName":"변경된 닉네임"
}

수신한 데이터

감사합니다.

profile
잘하고 싶은 주니어 백엔드 개발자 입니다.

0개의 댓글