IT 개발 라이프/Back_End

Jackson 기반 JSON 유틸리티와 커스텀 ObjectMapper 적용하기

10Biliion 2025. 1. 9. 12:12

 

 

Java 애플리케이션에서 JSON 처리는 필수적입니다. 데이터를 직렬화(Serialize)하여 JSON 문자열로 변환하거나, 역직렬화(Deserialize)하여 객체로 변환하는 작업은 매우 빈번하게 이루어집니다. Jackson 라이브러리를 활용하여 JSON 처리를 간단하고 일관되게 구현하는 방법을 소개합니다. 특히 ObjectMapper를 확장한 커스텀 클래스와 JSON 유틸리티를 연계하여 효율성을 높이는 방법을 중점적으로 살펴보겠습니다.


1. StandardObjectMapper: 커스텀 ObjectMapper 구현

StandardObjectMapper는 Jackson의 ObjectMapper를 확장한 클래스입니다. JSON 처리에서 공통적으로 필요한 설정을 관리하며, 싱글톤 패턴으로 구현되었습니다.

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import java.text.SimpleDateFormat;

public final class StandardObjectMapper extends ObjectMapper {

    public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ssXXX";

    public StandardObjectMapper() {
        super();
        SimpleModule simpleModule = new SimpleModule();
        simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
        simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
        this.registerModule(simpleModule);
        this.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true) // 작은따옴표 허용(필드명을 감싸는 문자로 "뿐만 아니라 '도 허용. ex) {'key': "value"})
            .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)  // 알 수 없는 필드 무시(json에 있는 필드가 object에 없을 경우 발생시키는 오류를 비활성화)
            .setDateFormat(new SimpleDateFormat(DEFAULT_DATE_FORMAT)); // 날짜 포맷 설정
    }

    public static StandardObjectMapper getInstance() {
        return InstanceHolder.INSTANCE;
    }

    private static final class InstanceHolder {
        private static final StandardObjectMapper INSTANCE = new StandardObjectMapper();
    }
}

주요 설정

  • StandardObjectMapper: Jackson의 ObjectMapper를 상속받아 커스터마이징.
  • DEFAULT_DATE_FORMAT: JSON에서 날짜와 시간을 처리할 기본 포맷(ISO 8601 형식). 예: 2023-12-31T23:59:59+09:00
  • SimpleModule:
    • Jackson의 Module 객체로, 사용자 정의 직렬화/역직렬화 로직을 추가 가능.
    • Long 타입과 long 기본 타입을 문자열로 변환하도록 설정. (ToStringSerializer.instance 사용)
  • registerModule:
    • 위에서 설정한 SimpleModule을 ObjectMapper에 등록.
    • JSON 직렬화 시, Long 값이 숫자가 아닌 문자열로 변환됨.
  • ALLOW_SINGLE_QUOTES:
    • 작은따옴표(')를 JSON 문자열로 허용.
  • FAIL_ON_UNKNOWN_PROPERTIES:
    • JSON 데이터의 필드가 매핑 대상 클래스에 없는 경우, 기본적으로 예외를 발생시키지만 이 설정으로 예외를 무시.
  • setDateFormat:
    • JSON의 날짜/시간 필드를 DEFAULT_DATE_FORMAT으로 직렬화/역직렬화.
  • getInstance:
    • StandardObjectMapper의 인스턴스를 반환.
    • 싱글톤 패턴 사용으로 전역에서 동일한 객체를 재사용.
  • InstanceHolder:
    • 클래스가 로드될 때 StandardObjectMapper 인스턴스를 생성(지연 초기화).
    • Thread-safe하고 메모리 효율적인 싱글톤 구현 방식.

2. JsonUtils: JSON 유틸리티 클래스

JsonUtils는 JSON 데이터를 직렬화/역직렬화하는 정적 메서드를 제공하는 유틸리티 클래스입니다. 내부적으로 StandardObjectMapper를 사용하여 Jackson의 기능을 간단히 사용할 수 있도록 추상화했습니다.

import java.io.IOException;
import java.io.Reader;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;

public final class JsonUtils {

    private JsonUtils() {
        throw new UnsupportedOperationException();
    }

    private static ObjectMapper getObjectMapper() {
        return StandardObjectMapper.getInstance();
    }

    public static String serialize(final Object value) {
        try {
            return getObjectMapper().writeValueAsString(value);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
    }

    public static byte[] serializeAsBytes(final Object value) {
        try {
            return getObjectMapper().writeValueAsBytes(value);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static <T> T deserialize(final String json, final Class<T> clazz) {
        try {
            return getObjectMapper().readValue(json, clazz);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static <T> T deserialize(final String json, final TypeReference<T> typeReference) {
        try {
            return getObjectMapper().readValue(json, typeReference);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static <T> T deserialize(final Object json, final Class<T> clazz) {
        try {
            return getObjectMapper().convertValue(json, clazz);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static <T> T deserialize(final Object json, final TypeReference<T> typeReference) {
        try {
            return getObjectMapper().convertValue(json, typeReference);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static <T> T deserialize(final byte[] data, final Class<T> clazz) {
        try {
            return getObjectMapper().readValue(data, clazz);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static <T> T deserialize(final byte[] data, final TypeReference<T> typeReference) {
        try {
            return getObjectMapper().readValue(data, typeReference);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static <T> T deserialize(final Reader reader, final Class<T> clazz) {
        try {
            return getObjectMapper().readValue(reader, clazz);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

주요 메서드

  • serialize: 객체를 JSON 문자열로 직렬화.
  • serializeAsBytes: 객체를 바이트 배열로 직렬화.
  • deserialize:
    • JSON 문자열 또는 객체를 POJO(Class 타입)로 변환.
    • 제네릭 타입 데이터를 처리하기 위해 TypeReference를 지원.
  • convertValue: 객체를 다른 객체 타입으로 변환할 때 사용.

3. 두 클래스의 협력 구조

JSON 직렬화

MyObject obj = new MyObject();
String jsonString = JsonUtils.serialize(obj);
System.out.println(jsonString);
  • JsonUtils.serialize를 호출하면 StandardObjectMapper의 싱글톤 인스턴스를 사용하여 JSON 문자열로 변환합니다.

JSON 역직렬화

String json = "{\"id\":1,\"name\":\"John\"}";
MyObject obj = JsonUtils.deserialize(json, MyObject.class);
System.out.println(obj);
  • JsonUtils.deserialize를 호출하면 JSON 문자열을 MyObject 타입 객체로 변환합니다.

복잡한 타입 변환

String json = "[{\"id\":1,\"name\":\"John\"}, {\"id\":2,\"name\":\"Jane\"}]";
List<Map<String, Object>> list = JsonUtils.deserialize(json, new TypeReference<List<Map<String, Object>>>(){});
System.out.println(list);
  • TypeReference를 활용하여 복잡한 데이터 구조도 쉽게 처리합니다.

4. 주요 장점

재사용성

  • StandardObjectMapper를 통해 Jackson 설정을 전역적으로 재사용할 수 있습니다.

간편성

  • JsonUtils는 JSON 처리 로직을 추상화하여 코드의 간결성과 가독성을 높입니다.

일관성

  • 모든 JSON 처리 작업에서 동일한 설정(DEFAULT_DATE_FORMAT, ALLOW_SINGLE_QUOTES 등)을 적용하여 예외 상황을 줄입니다.

확장성

  • 추가적인 설정이 필요할 경우 StandardObjectMapper를 수정하기만 하면 모든 JSON 처리에 반영됩니다.