gengminy
갱미니의 코딩일지
gengminy
전체 방문자
오늘
어제
  • 분류 전체보기 (61)
    • 🚀 프로젝트 (16)
      • 🎸 고스락 티켓 (4)
      • 🌞 내친소 (5)
      • 🥁 두둥 (7)
    • 📡 백엔드 (31)
      • 🌱 Spring Boot (13)
      • 🐱 Nest.js (10)
      • ⭐ Node.js (8)
    • 🏭 Infra (11)
      • ⚙ 준비를 위한 준비 (2)
      • 🥑 AWS (3)
      • 🐳 Docker (3)
      • ⚡ Github Actions (3)
    • 🌊 프론트엔드 (1)
      • 🌌 React.js (1)
    • 😎 주저리 (2)

블로그 메뉴

  • 💻Github
  • 📸Instagram
  • ✨Blog

공지사항

인기 글

태그

  • oauth2
  • JWT
  • nodejs
  • AWS
  • 네스트
  • 스프링
  • 도커
  • JSON
  • GithubActions
  • springboot
  • OAuth
  • nestjs
  • nest
  • SlackAPI
  • 슬랙알림
  • github
  • Spring
  • docker
  • 깃헙액션
  • 스프링부트

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
gengminy

갱미니의 코딩일지

[Spring] 스프링 Custom Enum Deserializer 구현으로 JSON Enum null 로 파싱하기
🚀 프로젝트/🥁 두둥

[Spring] 스프링 Custom Enum Deserializer 구현으로 JSON Enum null 로 파싱하기

2023. 2. 21. 16:57

https://gengminy.tistory.com/48

 

[Spring] 스프링 Enum Validator Reflection 으로 개선 및 구현하기

https://gengminy.tistory.com/47 [Spring] 스프링에서 Enum 클래스 Validation 추가하기 (Enum JSON parse error 해결) 스프링에서 일반적으로 RequestBody 의 값을 Validation 하는 방법 스프링 MVC 에서 @Valid 어노테이션을

gengminy.tistory.com

 

앞서 스프링에서 Custom Enum Constraint Validator 를 Reflection 으로까지 구현했다.

하지만 딱 한 가지가 남았는데....

보기 싫었던 다음과 같은 반복적인 코드가 있다.

 

// Enum Validation 을 위한 코드, enum 에 속하지 않으면 null 리턴
@JsonCreator
public static EventStatus fromEventStatus(String val) {
    return Arrays.stream(values())
            .filter(type -> type.getName().equals(val))
            .findAny()
            .orElse(null);
}

Request 에서 제공한 Enum 필드에 대해 역직렬화 해주는 @JsonCreator

@JsonCreator 를 구현하지 않는다면

null 값을 대입하는 대신 JSON Parse Error 를 발생시킨다.

 

그래서 Enum Validation 을 위해선

Enum 에 속하지 않는 Constant 에 대해서 null 을 반환하도록 할 필요가 있다.

 

하지만 Enum 마다 반복적으로 구현해야 해서 이를 줄이고 싶었다.

 

여러 가지 방법을 시도하고 실패를 반복하며....

결국 구글링으로 방법을 찾았다!

 

 

📌 원하고자 하는 목표

@Getter
@AllArgsConstructor
@EnumClass
public enum EventStatus {
    PREPARING("PREPARING", "준비중"),
    OPEN("OPEN", "진행중"),
    CALCULATING("CALCULATING", "정산중"),
    CLOSED("CLOSED", "지난공연"),
    DELETED("DELETED", "삭제된공연");

    private final String name;
    @JsonValue private final String value;
}

위 처럼 @EnumClass 라는 커스텀 어노테이션 하나로

@JsonCreator 에서 실행하는 로직을 축약하고 싶었다.

 

📝 EnumClass

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonDeserialize(using = CustomEnumDeserializer.class)
public @interface EnumClass {}

커스텀 어노테이션 @EnumClass 를 정의한다.

@JsonDeserialize 라는 어노테이션을 통해 사용하고 싶은 Deserializer 를 지정할 수 있다.

 

이런 식으로 Jackson 관련 어노테이션을 상속시킬 때는

반드시 @JacksonAnnotationsInside 를 붙여야한다.

 

그렇지 않으면 이 어노테이션 자체에 Jackson 관련 코드가 적용되어서

원하지 않는 방향으로 실행이 된다.

 

 

📝 CustomEnumDeserializer

public class CustomEnumDeserializer extends StdDeserializer<Enum<?>>
        implements ContextualDeserializer {

    public CustomEnumDeserializer() {
        this(null);
    }

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

    @SuppressWarnings("unchecked")
    @Override
    public Enum<?> deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
        JsonNode jsonNode = jp.getCodec().readTree(jp);
        JsonNode nameNode = jsonNode.get("name");
        if (nameNode == null) return null;
        String text = jsonNode.asText();
        Class<? extends Enum> enumType = (Class<? extends Enum>) this._valueClass;
        return Arrays.stream(enumType.getEnumConstants())
                .filter(constant -> constant.name().equals(text))
                .findAny()
                .orElse(null);
    }

    @Override
    public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property)
            throws JsonMappingException {
        return new CustomEnumDeserializer(property.getType().getRawClass());
    }
}

모든 Enum 인스턴스에 대해 적용하기 위해 제네릭으로 Enum<?> 을 적용한다.

 

또 주목할만한 점은 ContextualDeserializer 를 implementation 하는 점인데

현재 Context 의 Target 클래스 타입을 통해 Deserializer 를 재정의할 필요가 있기 때문이다.

 

그렇지 않다면 현재 타입을 알 수 없어 this._valueClass 호출 시 NPE 가 발생한다.

 

Enum 타입이 고정되어 있다면 직접 Status.class 같이 생성자에 넣어주면 되지만,

현재 모든 추상 클래스 타입에 대해 적용해야하기 때문에 이 과정이 반드시 필요하다.

 

deserialize 구현부에서는 jsonNode 의 "name" 을 가져와

Enum Constant 의 name 으로 비교를 수행한다.

 

nameNode 로 굳이 한 번 더 나누어준 이유는

asText() 메소드가 null 에 대해 수행할 수 있는 가능성이 있기 때문

 

 

아주 깔끔해졌다

 

없는 Enum Constant 에 대해 정상적으로 null 을 반환하고 Validation 해준다.

이로써 Enum 관련 코드가 아주 깔끔해졌다!

 

 

🔍 레퍼런스

https://d2.naver.com/helloworld/0473330

 

위의 글이 많은 도움이 되었다.

 

 

🌎 구현 과정 관련 게시글

📎 Custom Enum Validator 구현하기

 https://gengminy.tistory.com/47

📎 Reflection 을 이용하여 Enum Validator 개선하기

https://gengminy.tistory.com/48

📎 Custom Enum Deserializer 구현하여 Enum 에 없는 값 null 로 파싱하기

https://gengminy.tistory.com/49

 

 

 

저작자표시 (새창열림)

'🚀 프로젝트 > 🥁 두둥' 카테고리의 다른 글

[Spring] 스프링 500 에러 발생 시 Slack 알림 전송하기 (슬랙 봇)  (0) 2023.03.15
[Spring] 스프링 Slack 메세지 전송하기 (Incoming WebHooks 활용하여 슬랙봇 만들기)  (0) 2023.03.03
[Spring] 스프링 날짜 타입 JSON 변환 및 포맷팅하기 - @JsonFormat, @JacksonAnnotationsInside  (0) 2023.02.27
[Spring] 스프링 Enum Validator Reflection 으로 개선 및 구현하기  (0) 2023.02.21
[Spring] 스프링에서 Enum 클래스 Validation 구현하기 (Enum JSON parse error 해결)  (0) 2023.02.21
    '🚀 프로젝트/🥁 두둥' 카테고리의 다른 글
    • [Spring] 스프링 Slack 메세지 전송하기 (Incoming WebHooks 활용하여 슬랙봇 만들기)
    • [Spring] 스프링 날짜 타입 JSON 변환 및 포맷팅하기 - @JsonFormat, @JacksonAnnotationsInside
    • [Spring] 스프링 Enum Validator Reflection 으로 개선 및 구현하기
    • [Spring] 스프링에서 Enum 클래스 Validation 구현하기 (Enum JSON parse error 해결)
    gengminy
    gengminy
    코딩

    티스토리툴바