[Java] Enum to Json / Enum to Object
- 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);
}
}
```
serialization/deserialization 시 Enum으로 바로 매핑 하도록 지원하는 애너테이션
```java
public enum CyberSourceReasonCode {
SUCCESS("100")
private String code;
}
```
```java
public class PayerAuthEnrollResponse {
private CyberSourceReasonCode reasonCode;
```
- 요렇게 되어 있는 상황에서, reasonCode로 들어오는 값이 `` SUCCESS``이면 바로 Enum으로 매핑되지만, `` 100``이나 `` 475`` 같은 code 필드에 해당하는 값이라고 하면 바로 Enum으로 매핑되지 않는다
- 이를 바로 Enum으로 매핑하기 위해서는 어떻게 해야할까?
- code 필드에 해당하는 값을 수신했을 때 바로 Enum으로 매핑하기 위해서는 code 필드에 @JsonValue 를 붙여주면 된다. (deserialization)
- Enum을 json 변환 했을 때도 code에 해당하는 값으로 변환된다. (serialization)
- NOTE ) 원래 @JsonValue는 serialization 시에 어떤 값으로 할 것인지만 지정하고, deserialization 시에 어떤 값으로 받을지는 @JsonCreator를 사용해 지정해주어야 하지만, Enum의 경우에 한하여 deserialization 시에 어떤 값을 매핑에 사용할지도 지정한다. (docs에 나와있다.)
'Java Stack > Java' 카테고리의 다른 글
[Java] HmacUtils, Mac이 thread-safe하지 않다? (0) | 2021.03.17 |
---|---|
[Java] Collection 초기화 (0) | 2020.11.02 |
[Spring] resources 경로 문제 (0) | 2020.03.18 |
[Effective Java] 12장 직렬화 (0) | 2020.02.28 |
[Effective Java] 11장 동시성 + collection 유틸 메서드 (0) | 2020.02.28 |