엄범

 

  • EnumConstant 하나를 `` {fieldName:fieldValue}`` 형식으로 매핑하고 싶은 경우가 있다 ( 주로 뷰로 전달해야 할 때. )
  • 기본적으로 jackson의 ObjectMapper는 serialize/deserialize 할 때 Enum 코드만 내려주도록 되어 있다.
    • 즉, ``java PaymentCode.CARD``를 변환하면 ``java "CARD"``가 된다.
    • ``java {code=1, koName="카드", enName="card"}`` 형태로 내려주려면, 뭔가 해줘야 한다

 

방법 1 ) Jackson / ObjectMapper를 사용하고 싶은 경우

방법 1-1 ) @JsonFormat.Shape.OBJECT

  • key:value 쌍을 내려주고 싶은 경우를 대비해 jackson은 ``java JsonFormat.Shape.OBJECT`` 를 제공하고 있다.
  • https://fasterxml.github.io/jackson-annotations/javadoc/2.7/  
    • 아래와 같이 지정해주면 알아서 serialize 시 key=value 형식으로 매핑해준다.
    • deserialize 시에는 그냥 CODE만 받아도 enum으로 받아준다. (controller에서 받을 때 등등)

```java

@JsonFormat(shape = JsonFormat.Shape.OBJECT)

public enum EnumCls

```

 

  • 반면, 붙어 있는 애너테이션을 무시하고 싶다면? 

```java

objectMapper.disable(MapperFeature.USE_ANNOTATIONS);

```

 

방법 1-2 ) key:value 매핑해주는 Serializer를 등록해서 사용하고 싶은 경우

  • 경우에 따라 Annotation을 붙일 수 없는 경우도 있는데,
    • 다른 라이브러리에 있는 Enum이거나,
    • 붙이게 되면 항상 key-value 형식으로 변환되기 때문에 기존의 Code만으로 변환되는 것과 key-value 형식으로 변환되는 것을 선택해야 한다는 요구사항이 있을 때.
  • 그럴 때는 다음과 같이 BeanSerializer를 통해 변환하도록 유도하는 방식을 사용할 수 있다.
    • 사실 이렇게 library 헤집어서 쓰는걸 별로 좋아하지는 않지만, 요구사항이 있다면 어쩔 수 없지.
  • 처음부터 모든 enum에 JsonFormat을 붙이는 방법이 더 좋지 않을까 싶은게, 결국 이 방법은 컨트롤러에서 리턴할 때 EnumType 이 아니라, 직접 변환이 끝난 Map<String, Object>를 리턴할 수 밖에 없기 때문.

 

```java

public class ObjectMapperUtil {

    /**
     * 기본적으로 jackson은 Enum은 EnumSerializer를 사용해서 변환하며, 이렇게 변환한 결과는 enum {CODE}가 된다.
     * (e.g., CardCode.CCBC 변환 결과는 "CCBC" 가 된다.
     *
     * 그러나 @JsonFormat(shape = JsonFormat.Shape.OBJECT) 이 붙어 있는 enum이 넘어오면, BeanSerializer를 사용해서 변환하며,
     * 이렇게 변환한 결과는 {"field-key":"field-value"} 쌍이 된다.
     * 이 BeanSerializer는 일반적인 객체를 변환할 때 사용하는 serializer이므로, 변환 결과가 key-value가 되는 것이다.
     *
     * 해당 메서드는 @JsonFormat이 붙지 않은 Enum도 key-value 형식으로 변환해주는 ObjectMapper를 구현 및 제공한다.
     * 이를 위해 BeanSerializer를 직접 반환 받아 Custom Serializer로 등록하여 Enum 타입에 대해서도 BeanSerializer가 동작하도록 했다.
     * BeanSerializerFactory.findBeanSerializer(sp, javaType, beanDesc)는
     * BeanSerializer를 얻는 메서드 중 추상화 수준이 가장 높은 메서드이다.
     *
     * @param type
     * @return
     */
    public static ObjectMapper getEnumObjectMapper(Class<?> type) {

        ObjectMapper objectMapper = new ObjectMapper();

 

        BeanSerializerFactory sf = (BeanSerializerFactory)objectMapper.getSerializerFactory();

        SerializationConfig config = objectMapper.getSerializationConfig();

        SerializerProvider sp = objectMapper.getSerializerProviderInstance();

 

        JavaType javaType = config.constructType(type);

        BeanDescription beanDesc = config.introspect(javaType);

        ((BasicBeanDescription) beanDesc).removeProperty("declaringClass");

 

        JsonSerializer<Object> serializer;
        try {
            serializer = sf.findBeanSerializer(sp, javaType, beanDesc);
        } catch (JsonMappingException e) {
            throw new RuntimeException(e);
        }

        SimpleModule module = new SimpleModule();
        module.addSerializer(serializer);
        objectMapper.registerModule(module);

 

        return objectMapper;
    }
}

```

  • class 안에 있는 field의 타입이 Enum인 경우에도 동작하는데, 이런 Enum이 여러 개인 경우에는 serializer를 여러 개 등록해줘야 하므로 메서드를 좀 수정할 필요가 있음!

 

 

방법 2 ) Enum 클래스 내에서 메서드로 제공하고 싶은 경우

  • 그냥 ``java map.put("koName", this.koName);`` 이런 식으로 해도 되지만... 아래는 reflection을 이용해 한방에 처리하는 코드.

```java

public Map<String, String> toMap() {

    return Arrays.stream(this.getDeclaringClass().getDeclaredFields())

        .filter(field -> !field.isSynthetic() && !field.isEnumConstant())

        .collect(Collectors.toMap(

            field -> field.getName(),

            field -> getFieldValue(field)));

}

 

private String getFieldValue(Field field) {

    try {

        return (String)field.get(this);

    } catch (IllegalAccessException e) {

        throw new RuntimeException(e);

    }

}

```

 

필드값 수신 시 Enum으로 바로 매핑 하는 애너테이션!

```java

public enum CyberSourceReasonCode {
    SUCCESS("100")

    

    private String code;

}

```

```java

public class PayerAuthEnrollResponse {
    private CyberSourceReasonCode reasonCode;

```

  • 요렇게 되어 있는 상황에서, reasonCode로 들어오는 값이 `` 100``이나 `` 475`` 같은 코드값이라고 하면, 바로 SUCCESS로 매핑이 되지 않는다. (deserialize 시.)
  • 바로 매핑하도록 지원해주는 애너테이션으로 `` @JsonValue`` 가 있음! 이걸 필드 code 위에 붙여준다!
  • 근데 문제는 json으로 serialize 할 때에도 100... 형태로 변환해버린다는 점... serialize/deserialize 비대칭으로 가려면 Custom Serializer를 정의하는 수 밖에 없는 듯? 아니면 별도 UserResponse 클래스를 쓰거나... 

 

'Languages & Frameworks > Java' 카테고리의 다른 글

[Java8] CompletableFuture  (0) 2020.10.13
[Java] Enum to Json / Enum to Object  (0) 2020.04.27
[Spring] resources 경로 문제  (0) 2020.03.18
[Effective Java] 12장 직렬화  (0) 2020.02.28
[Effective Java] 11장 동시성  (0) 2020.02.28
[Effective Java] 10장 예외  (0) 2020.02.27