기술과 산업/언어 및 프레임워크
Java JSON 처리 실전 시리즈 8화 – 커스텀 Serializer와 Deserializer로 JSON 변환 제어하기
B컷개발자
2025. 5. 19. 10:57
728x90
Jackson에서 커스텀 Serializer 및 Deserializer를 구현해 특수 포맷, 민감 정보, 복합 필드에 대한 JSON 직렬화/역직렬화를 제어하는 방법을 실무 예제 중심으로 설명합니다.
Spring Boot에서 기본적인 JSON 직렬화/역직렬화는 ObjectMapper가 자동으로 처리해줍니다. 그러나 아래와 같은 상황에서는 커스텀 Serializer 또는 Deserializer가 필요해집니다.
- 특정 필드를 Base64 인코딩 또는 마스킹 처리하고 싶을 때
- 날짜 포맷이 비표준이거나 복합적인 형태일 때
- 중첩된 객체를 평탄화하거나 축약해서 표현하고 싶을 때
- 클라이언트에 따라 JSON 포맷을 맞춤화하고 싶을 때
이번 글에서는 Jackson에서 커스텀 직렬화/역직렬화 클래스를 구현하고 적용하는 방법을 구체적인 예제와 함께 설명합니다.
1. 기본 구조: 커스텀 Serializer / Deserializer 클래스
커스텀 Serializer
public class MaskingSerializer extends JsonSerializer<String> {
@Override
public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeString("***" + value.substring(value.length() - 3));
}
}
커스텀 Deserializer
public class EmailDeserializer extends JsonDeserializer<String> {
@Override
public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
String raw = p.getText();
return raw.toLowerCase().trim();
}
}
2. DTO 필드에 적용하기
public class User {
private String name;
@JsonSerialize(using = MaskingSerializer.class)
@JsonDeserialize(using = EmailDeserializer.class)
private String email;
}
요청 JSON
{
"name": "홍길동",
"email": "ADMIN@EXAMPLE.COM "
}
역직렬화 후 객체 상태
user.getEmail(); // "admin@example.com"
직렬화된 JSON 응답
{
"name": "홍길동",
"email": "***com"
}
3. 커스텀 Serializer를 전역으로 등록하기
만약 특정 타입 전체에 대해 동일한 커스터마이징을 적용하고 싶다면 SimpleModule을 통해 전역 등록이 가능합니다.
@Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(String.class, new MaskingSerializer());
mapper.registerModule(module);
return mapper;
}
주의: String 전체에 마스킹을 적용하면 모든 문자열 필드에 동일한 로직이 적용되므로 주의가 필요합니다. 특정 DTO나 조건부로 제한하는 것이 바람직합니다.
4. 실무 응용 예시
(1) 주민등록번호 마스킹
public class RrnMaskingSerializer extends JsonSerializer<String> {
@Override
public void serialize(String rrn, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeString(rrn.replaceAll("^\\d{6}-\\d{1}", "******-*"));
}
}
(2) 금액 단위 포맷 처리 (예: 1,000,000 → 100만)
public class AmountShortenSerializer extends JsonSerializer<Long> {
@Override
public void serialize(Long amount, JsonGenerator gen, SerializerProvider serializers) throws IOException {
if (amount >= 1_000_000) {
gen.writeString((amount / 1_000_000) + "만");
} else {
gen.writeNumber(amount);
}
}
}
5. Deserializer에서 Enum 커스텀 매핑
public enum Role {
ADMIN, USER;
}
public class RoleDeserializer extends JsonDeserializer<Role> {
@Override
public Role deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
String value = p.getText().toUpperCase(Locale.ROOT);
try {
return Role.valueOf(value);
} catch (Exception e) {
return Role.USER;
}
}
}
이 방식은 잘못된 값이 들어올 때 안전하게 기본값으로 매핑해주는 데 유용합니다.
6. 유지보수 팁과 주의사항
- 커스텀 Serializer/Deserializer는 직관성이 떨어지므로 꼭 필요한 경우에만 사용
- 테스트 코드를 반드시 작성해 동작을 검증
- 불변 객체(record, Lombok 등)에서는 생성자 기반 역직렬화 설정을 함께 고려해야 함
- Jackson 버전이 낮으면 일부 동작이 제한될 수 있으므로 최소 2.10 이상 권장
다음 회차 예고
9화에서는 실무에서 종종 마주하는 문제 중 하나인 따옴표 없는 비표준 JSON 처리에 대해 다룹니다. ALLOW_UNQUOTED_FIELD_NAMES, ALLOW_SINGLE_QUOTES 등 Jackson의 유연한 파싱 기능과 보안상의 주의점을 함께 설명합니다.
728x90