Jackson枚举序列化与反序列化

本文主要记载Jackson按需求序列化枚举以及反序列化枚举的实现,踩过一些坑,所以做下笔记。Java默认序列化枚举的Name字段,比如ENUM_SEX_MAN(0),默认只会序列化ENUM_SEX_MAN。不满足实际需求。所以项目中需要做一些自定义。

背景

项目中使用到了王琦的体质辨识问题,最后结果是九种体质,这里使用了枚举的方式实现,参数比较多,比如key(保存到数据库的ID),name(体质名称),reduce(减法系数),remove(除法次数),enumName(枚举名称),需要将这些数据序列化后返回给前端。枚举默认只序列化Enum类的name字段,但是这里需要序列化所有字段,并且需要进入redis缓存。

枚举实现

根据需求,我们实现一个基础枚举接口,如下:

1
2
3
4
5
6
7
8
public  interface BaseEnum<T extends Serializable> extends IEnum<T> {
// 保存在数据库中的key
Integer key();
// 枚举别名
String realName();
// 枚举名称
String enumName();
}

这个接口是所有枚举的通用接口,基于此接口,我们再定义一个体质枚举接口

1
2
3
4
5
6
public  interface BaseConstEnum<T extends Serializable> extends BaseEnum<T> {
// 减法系数
float reduce();
` // 除法系数
float remove();
}

然后我们定义体质枚举,实现BaseConstEnum,如下所示

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
@Getter
@JsonDeserialize(using = ConstEnumDeserializer.class)
public enum ConstEnum implements BaseConstEnum<Integer> {
CONST_NO(-1,"无体质",0f,0f,"CONST_NO"),
CONST_PIN_HE(0,"平和质",8f,32f,"CONST_PIN_HE"),
CONST_QI_XU(1,"气虚质",8f,32f,"CONST_QI_XU"),
CONST_YANG_XU(2,"阳虚质",7f,28f,"CONST_YANG_XU"),
CONST_YIN_XU(3,"阴虚质",8f,32f,"CONST_YIN_XU"),
CONST_QI_YU(4,"气郁质",7f,28f,"CONST_QI_YU"),
CONST_TAN_SHI(5,"痰湿质",8f,32f,"CONST_TAN_SHI"),
CONST_TE_BIN(6,"特禀质",7f,28f,"CONST_TE_BIN"),
CONST_SHI_RE(7,"湿热质",6f,24f,"CONST_SHI_RE"),
CONST_YU_XUE(8,"血瘀质",7f,28f,"CONST_YU_XUE");

@EnumValue
private final Integer key;
private final String name;
// 减法系数
private final float reduce;
// 除法系数
private final float remove;
private final String enumName;

ConstEnum(Integer key, String name, float reduce, float remove,String enumName) {
this.key = key;
this.name = name;
this.reduce = reduce;
this.remove = remove;
this.enumName = enumName;
}

@Override
public Integer getValue() {
return key;
}

public static ConstEnum getConstById(Integer id) {
if (id == null) {
return CONST_NO;
}
if (id.equals(CONST_PIN_HE.getKey())) {
return CONST_PIN_HE;
} else if (id.equals(CONST_QI_XU.getKey())) {
return CONST_QI_XU;
} else if (id.equals(CONST_YANG_XU.getKey())) {
return CONST_YANG_XU;
} else if (id.equals(CONST_YIN_XU.getKey())) {
return CONST_YIN_XU;
} else if (id.equals(CONST_QI_YU.getKey())) {
return CONST_QI_YU;
} else if (id.equals(CONST_TAN_SHI.getKey())) {
return CONST_TAN_SHI;
} else if (id.equals(CONST_TE_BIN.getKey())) {
return CONST_TE_BIN;
} else if (id.equals(CONST_SHI_RE.getKey())) {
return CONST_SHI_RE;
} else if (id.equals(CONST_YU_XUE.getKey())) {
return CONST_YU_XUE;
}
return CONST_NO;
}

public static ConstEnum getConstByValue(String v) {
if (!StringUtils.hasLength(v)) {
return CONST_NO;
}
if (v.equals(CONST_PIN_HE.getName())) {
return CONST_PIN_HE;
} else if (v.equals(CONST_QI_XU.getName())) {
return CONST_QI_XU;
} else if (v.equals(CONST_YANG_XU.getName())) {
return CONST_YANG_XU;
} else if (v.equals(CONST_YIN_XU.getName())) {
return CONST_YIN_XU;
} else if (v.equals(CONST_QI_YU.getName())) {
return CONST_QI_YU;
} else if (v.equals(CONST_TAN_SHI.getName())) {
return CONST_TAN_SHI;
} else if (v.equals(CONST_TE_BIN.getName())) {
return CONST_TE_BIN;
} else if (v.equals(CONST_SHI_RE.getName())) {
return CONST_SHI_RE;
} else if (v.equals(CONST_YU_XUE.getName())) {
return CONST_YU_XUE;
}
return CONST_NO;
}

@Override
public float reduce() {
return reduce;
}

@Override
public float remove() {
return remove;
}

@Override
public Integer key() {
return key;
}

@Override
public String realName() {
return name;
}

@Override
public String enumName() {
return enumName;
}
}

序列化器实现

@EnumValue注解是Mybatis plus提供的,我们想要实现枚举中的字段转对象,可以通过jackson注解
@JsonFormat(shape = JsonFormat.Shape.OBJECT),也可以通过自定义json序列化器,这里我们自定义下json序列化器。如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@SuppressWarnings({ "unchecked", "rawtypes" })
public class ConstEnumSerializer extends JsonSerializer<BaseConstEnum> {

public static final ConstEnumSerializer instance = new ConstEnumSerializer();

@Override
public void serialize(BaseConstEnum value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeStartObject();
gen.writeNumberField("key",value.key());
gen.writeStringField("name",value.realName());
gen.writeStringField("enumName",value.enumName());
gen.writeNumberField("reduce",value.reduce());
gen.writeNumberField("remove",value.remove());
gen.writeEndObject();
}
}

序列化器实现了,比较简单,但是如果不实现反序列化器,name反序列化时就会报SerializationException异常,如下所示

1
org.springframework.data.redis.serializer.SerializationException: Could not read JSON: Cannot deserialize value of type `com.mayahx.stcm.api.entity.diagnose.enums.ConstEnum` from Object value

反序列化器实现

我们必须实现反序列化器,实现反序列化器需要实现JsonDeserializer,如下

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
@Slf4j
@SuppressWarnings({ "unchecked", "rawtypes" })
public class ConstEnumDeserializer extends JsonDeserializer<BaseConstEnum> {

public static final ConstEnumDeserializer instance = new ConstEnumDeserializer();

@Override
public BaseConstEnum deserialize(JsonParser p, DeserializationContext ctx) throws IOException {
JsonNode node = p.getCodec().readTree(p);
String currentName = p.currentName();
Object currentValue = p.getCurrentValue();
Class findPropertyType = BeanUtils.findPropertyType(currentName, currentValue.getClass());
JsonNode enumNode = node.get("enumName");
String asText;
if (enumNode!=null){
asText = node.get("enumName").asText();
if (StringUtils.hasLength(asText)){
return (BaseConstEnum) Enum.valueOf(findPropertyType,asText);
}else {
return (BaseConstEnum) Enum.valueOf(findPropertyType,node.asText());
}
}
return null;
}
}

我们通过判断enumName来进行转换枚举,有两种情况,如果是完整对象的反序列化,可以通过node.get("enumName").asText()获取枚举名称,如果是前端上传的枚举名称,则只能通过node.asText()转化。实现序列化器及反序列化器后,还需要将序列化器注册到mapper,并使用@JsonDeserialize(using = ConstEnumDeserializer.class)将反序列化器注解到枚举中。如下:

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
@Slf4j
public class JsonUtil {
private static final ObjectMapper mapper;
private static final JsonUtil json = new JsonUtil();

static {
mapper = new ObjectMapper();
SimpleModule sm = new SimpleModule();
sm.addSerializer(Long.class, ToStringSerializer.instance);
sm.addSerializer(Long.TYPE, ToStringSerializer.instance);
sm.addSerializer(BaseEnum.class,BaseEnumSerializer.instance);
sm.addSerializer(BaseConstEnum.class,ConstEnumSerializer.instance);
sm.addDeserializer(BaseEnum.class, BaseEnumDeserializer.instance);
sm.addDeserializer(BaseConstEnum.class, ConstEnumDeserializer.instance);
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.setSerializationInclusion(JsonInclude.Include.ALWAYS);
mapper.registerModule(sm);
}


public static JsonUtil getInstance(){
return json;
}

/**
* 对象转Str
* @param t 对象
* @return str类型
*/
public <T> String str(T t){
try {
return mapper.writeValueAsString(t);
} catch (JsonProcessingException e) {
logger.error("T to str fail. exception msg: {}",e.getMessage());
}
return null;
}

/**
* 根据Str转对象
* @param str str
* @param clz 对象类型
* @return 对象实体
*/
public <T> T strToObj(String str, Class<T> clz){
try {
return mapper.readValue(str,clz);
} catch (JsonProcessingException e) {
logger.error("T to str fail. exception msg: {}",e.getMessage());
}
return null;
}

/**
* 根据Str转List对象
* @param str str
* @param clz 对象类型
* @return List对象实体
*/
public <T> List<T> strToListObj(String str, Class<T> clz){
List<T> objList;
try {
objList = mapper.readValue(str, new TypeReference<List<T>>(){});
} catch (Exception e) {
logger.error(e.getMessage());
return null;
}
return objList;
}
}

现在就可以安心使用枚举进行操作了。

作者

Labradors

发布于

2022-03-24

更新于

2022-03-25

许可协议

评论