(Java) Enum to Json / Enum to Object
- EnumConstant 하나를
{fieldName:fieldValue}
형식으로 매핑하고 싶은 경우가 있다 ( 주로 뷰로 전달해야 할 때. ) - 기본적으로 jackson의 ObjectMapper는 serialize/deserialize 할 때 Enum 코드만 내려주도록 되어 있다.
- 즉,
PaymentCode.CARD
를 변환하면"CARD"
가 된다. {code=1, koName="카드", enName="card"}
형태로 내려주려면, 뭔가 해줘야 한다
- 즉,
방법 1 ) Jackson / ObjectMapper를 사용하고 싶은 경우
방법 1-1 ) @JsonFormat.Shape.OBJECT
- key:value 쌍을 내려주고 싶은 경우를 대비해 jackson은
JsonFormat.Shape.OBJECT
를 제공하고 있다. docs - 아래와 같이 지정해주면 알아서 serialize 시 key=value 형식으로 매핑해준다.
- deserialize 시에는 그냥 CODE만 받아도 enum으로 받아준다. (controller에서 받을 때 등등)
1
2
@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum EnumCls
- 반면, 붙어 있는 애너테이션을 무시하고 싶다면?
1
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>
를 리턴할 수 밖에 없기 때문.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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 클래스 내에서 메서드로 제공하고 싶은 경우
- 그냥
map.put("koName", this.koName);
이런 식으로 해도 되지만, 아래는 reflection을 이용해 한방에 처리하는 코드.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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으로 바로 매핑 하도록 지원하는 애너테이션
1
2
3
4
public enum CyberSourceReasonCode {
SUCCESS("100")
private String code;
}
1
2
3
public class PayerAuthEnrollResponse {
private CyberSourceReasonCode reasonCode;
}
- 요렇게 되어 있는 상황에서, reasonCode로 들어오는 값이
SUCCESS
이면 바로 Enum으로 매핑되지만,100
이나475
같은 code 필드에 해당하는 값이라고 하면 바로 Enum으로 매핑되지 않는다- 이를 바로 Enum으로 매핑하기 위해서는 어떻게 해야할까?
- code 필드에 해당하는 값을 수신했을 때 바로 Enum으로 매핑하기 위해서는 code 필드에 @JsonValue 를 붙여주면 된다. (deserialization)
- Enum을 json 변환 했을 때도 code에 해당하는 값으로 변환된다. (serialization)
원래 @JsonValue는 serialization 시에 어떤 값으로 할 것인지만 지정하고, deserialization 시에 어떤 값으로 받을지는@JsonCreator 를 사용해 지정해주어야 하지만, Enum의 경우에 한하여 deserialization 시에 어떤 값을 매핑에 사용할지도 지정한다. (docs 참고)
kotlin 에서는
@get:JsonValue
사용해야 동작 한다. 참고
This post is licensed under CC BY 4.0 by the author.