카테고리 없음
📝 TIL: @ValidEnum을 사용한 Enum 값 검증 및 Jackson 설정
creator7087
2025. 4. 21. 21:03
1. 목표
- DTO에서 Enum 값 검증을 통해 유효하지 않은 값에 대해 커스텀 예외를 던지고, 상세한 에러 메시지를 반환하는 방법을 학습했습니다.
- Jackson 설정을 통해 Enum 값을 매핑하고, 대소문자 무시 설정을 적용했습니다
2. 핵심 개념
a. @ValidEnum 커스텀 어노테이션
- @ValidEnum은 enum 값을 검증하기 위해 사용되는 커스텀 어노테이션입니다.
- 이 어노테이션을 통해 enum 필드 값이 유효한 값인지 검증하고, 유효하지 않으면 커스텀 예외를 던질 수 있습니다.
b. EnumValidator
- EnumValidator는 @ValidEnum 어노테이션을 실제로 처리하는 유효성 검증 클래스입니다.
- Enum 클래스에서 지정된 값만을 유효한 값으로 인정하고, 유효하지 않으면 예외를 발생시킵니다.
c. Jackson 설정
- Jackson의 DeserializationFeature.ACCEPT_CASE_INSENSITIVE_ENUMS 옵션을 사용하여 대소문자 무시를 처리합니다.
- DTO에서 enum 타입을 직접 받도록 설정하고, String 대신 Enum 타입을 받도록 수정합니다.
3. @ValidEnum 커스텀 어노테이션 구현
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = EnumValidator.class)
@Documented
public @interface ValidEnum {
String message() default "유효하지 않은 값입니다.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
Class<? extends Enum<?>> enumClass();
boolean ignoreCase() default false;
}
- enumClass: 유효성 검증할 enum 클래스를 지정.
- ignoreCase: 대소문자를 구분할지 여부를 설정할 수 있는 옵션.
4. EnumValidator 클래스
public class EnumValidator implements ConstraintValidator<ValidEnum, String> {
private Class<? extends Enum<?>> enumClass;
private boolean ignoreCase;
@Override
public void initialize(ValidEnum annotation) {
this.enumClass = annotation.enumClass();
this.ignoreCase = annotation.ignoreCase();
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null || value.isEmpty()) {
return true; // 다른 검증 어노테이션이 처리
}
return Arrays.stream(enumClass.getEnumConstants())
.anyMatch(e -> {
String name = e.name();
return ignoreCase ? name.equalsIgnoreCase(value) : name.equals(value);
});
}
}
- initialize: 어노테이션에서 지정된 enumClass와 ignoreCase 값을 초기화.
- isValid: 필드 값이 유효한 enum 값인지 체크합니다.
5. Category enum과 PostRequest DTO 예시
public enum Category {
NOTICE("공지사항"),
FREE("자유 게시판"),
QNA("질문 게시판");
private final String description;
@JsonCreator
public static Category fromString(String value) {
try {
return Category.valueOf(value.toUpperCase()); // 대소문자 무시
} catch (IllegalArgumentException e) {
throw new InvalidRequestException("유효하지 않은 Category입니다.");
}
}
@JsonValue
public String getDescription() {
return description;
}
}
- fromString 메서드에서 대소문자 무시와 유효하지 않은 값 처리를 합니다.
- @JsonCreator를 사용하여 Jackson에서 JSON 값을 Enum으로 매핑합니다.
PostRequest DTO
public class PostRequest {
@NotBlank
private String title;
@NotBlank
private String content;
@NotNull
@ValidEnum(enumClass = Category.class, message = "유효하지 않은 카테고리입니다. 유효한 카테고리 값은 NOTICE, FREE, QNA 입니다.")
private Category category;
// Getter, Setter
}
category는 Category enum 타입으로 받으며, 유효성 검증을 위해 @ValidEnum을 사용합니다.
6. Jackson 설정 (대소문자 무시)
@Configuration
public class JacksonConfig {
@Bean
public Jackson2ObjectMapperBuilderCustomizer enumCaseInsensitive() {
return builder -> builder.featuresToEnable(
DeserializationFeature.ACCEPT_CASE_INSENSITIVE_ENUMS
);
}
}
이 설정을 통해 대소문자 구분 없이 enum 값을 매핑할 수 있습니다. 예를 들어 "category": "notice"가 "category": "NOTICE"로 변환됩니다
7. Controller에서의 사용 예시
@PostMapping("/posts")
public ResponseEntity<PostResponse> createPost(@Valid @RequestBody PostRequest postRequest) {
PostResponse response = postService.createPost(postRequest);
return ResponseEntity.ok(response);
}
@Valid 어노테이션으로 유효성 검증을 트리거하고, 유효하지 않으면 400 Bad Request와 함께 커스텀 에러 메시지가 반환됩니다.
8. 최종 정리
- @ValidEnum 어노테이션을 사용하여 enum 값 검증을 처리하고, 유효하지 않은 값이 입력되면 커스텀 예외와 상세한 에러 메시지를 반환합니다.
- Jackson 설정을 통해 대소문자 무시를 적용하고, DTO에서 Enum 값을 직접 받도록 설정할 수 있습니다.
- @JsonCreator와 @JsonValue를 활용해 JSON과 Enum의 매핑을 커스터마이즈할 수 있습니다.
오늘 배운 내용은 유효성 검증과 Jackson을 활용한 Enum 처리에 관한 중요한 개념들을 다룬 것으로, 실제 프로젝트에서 유용하게 사용할 수 있습니다. 🎉