Projects/Personal project - 게시판 만들기

UnexpectedTypeException과 DTO에서 Enum타입 사용

마손리 2023. 5. 24. 20:36

처음 시작은 게시판의 게시글을 post 하기 위한 DTO를 작성하고 요청을 보내 테스트하던중 UnexpectedTypeException 예외가 발생하였다.

(해당 예외 이름에서 알 수 있듯이 요청으로 받은 JSON 파일이 다른 타입을 제공하고 있어서 발생한 예외이다. 이것만 좀더 일찍알았더라면 2시간을 허비하지 않았을텐데...)

 

발생한 예외의 DTO와 Enum 코드

 

DTO

public class PostsDto {
    @Getter
    @Builder
    @Setter
    public static class Post{
    	...
    	...
        @NotBlank
        private String title;
        @NotBlank // 예외 발생지점
        private Posts.PostStatus postStatus; 
        @NotBlank
        private String text;

    }
}

 

Enum

    public enum PostStatus{
        POST_PUBLIC("public post"),
        POST_PRIVATE("private post"),
        POST_DELETED("deleted post");
        
	@Getter
        private String status;

        PostStatus(String status) {
            this.status = status;
        }
    }

 

요청 JSON

{
	"text": "the Contents",
	"title":"the Title",
	"postStatus": "public post"
}

 

구글링을 해보니 JSON 포맷은 숫자형과 문자형으로만 타입이 정해저 있다보니 Spring MVC의 HandlerAddapter에서 맵핑이 안되 발생하는 예외이므로 역직렬화를 위한 코드수정이 필요했다.

 

1차 시도 - Enum 코드수정

    public enum PostStatus{
    ...
    ...
        @JsonCreator
        public static PostStatus from(String sub) {
            for (PostStatus status : PostStatus.values()) {
                System.out.println(status.getStatus());
                if (status.getStatus().equals(sub)) {
                    return status;
                }
            }
            return null;
        }

        @JsonValue
        public String getStatus() {
            return status;
        }

    }

위와 같이 역직렬화를 위한 코드를 추가해 주었지만 같은 예외가 계속 발생하였다.

 

무엇이 문제였을까... 

해당 DTO의 예외발생지점의 @NotBlank validator를 지워 보았더니 잘 작동하였다.

 

하지만 내가 원한건 유효성검증이 잘 이뤄지면서 해당 요청을 받아야 했기에 또 다시 구글링 시작... 이후 새로운 validator를 만들어 보았다.

 

 

2차 시도 - 커스텀 유효성 검증 에너테이션 추가

@Documented
@Constraint(validatedBy = EnumValidatorConstraint.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, 
        ElementType.FIELD, 
        ElementType.ANNOTATION_TYPE, 
        ElementType.CONSTRUCTOR, 
        ElementType.PARAMETER, 
        ElementType.TYPE_USE})
@NotNull
public @interface EnumValidator {
    Class<? extends Enum<?>> enumClass();
    String message() default "must be any of enum {enum}";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}
public class EnumValidatorConstraint implements ConstraintValidator<EnumValidator, String> {

    Set<String> values;

    @Override
    public void initialize(EnumValidator constraintAnnotation) {
        System.out.println(constraintAnnotation.enumClass());
        values = Stream.of(constraintAnnotation.enumClass().getEnumConstants())
                .map(Enum::name)
                .collect(Collectors.toSet());
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        return values.contains(value);
    }
}

 

위와 같이 유효성검증을 위해 새로운 에너테이션을 만들어준 뒤 해당 에너테이션을 예외발생지점에 명시해 주었다.

        @EnumValidator(enumClass = Posts.PostStatus.class)
        private Posts.PostStatus postStatus;

 

그리고 다시 테스트를 해보았는데... 잉???  또 다시 같은 예외가 발생했다??

 

결국 구글링을 다시 해보았는데... 정답은 바로 예외 메세지에 담겨 있었다...

 

@NotBlank 사용중 PostStatus라는 허락되지 않은 타입이 정의되있어서 발생한 예외인것인데 

 

@NotBlank와 @NotEmpty는 문자형, 배열 혹은 컬렉션 타입에서만 사용이 가능하다.

분명 어딘가에서 읽었던 내용인데 무심코 지나친 대가가 크구나...

 

 

결론

@NotNull의 경우 모든 타입이 사용가능하며 String타입만 지정해 주지 않는다면 Empty 값이 들어올 수 없으므로 @NotNull을 사용하자.

 

 

결과 코드

DTO

public class PostsDto {
    @Getter
    @Builder
    @Setter
    public static class Post{
    	...
        ...
        @NotNull // @NotNull 사용
        private Posts.PostStatus postStatus;
        ...
        ...
    }
}

 

Enum

public enum PostStatus{
    POST_PUBLIC("public post"),
    POST_PRIVATE("private post"),
    POST_DELETED("deleted post");

    private String status;

    PostStatus(String status) {
        this.status = status;
    }

// @NotNull을 사용하더라도 @JsonValue를 이용하여 해당 Enum에 대한 getter를 만들어 주어야 한다.
    @JsonValue 
    public String getStatus() {
        return status;
    }

}