@JsonFormat(shape = JsonFormat.Shape.OBJECT)的作用
作用
这个注解的作用是将枚举类像对象一样序列化。
为什么使用?
如果不以这种方式,那么枚举类的值就仅仅是他的名字(枚举类自带的name字段),这样前后端协作时就不好知道你的枚举类都是什么含义,还要自己写额外的文档,一旦修改或者增加则还要修改文档,很不方便。
使用方式
定义这样一个接口,让需要以这种方式序列化的枚举类都实现这个接口即可
@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public interface BaseEnum {}
一般会这样定义接口:要求枚举类至少有个对其自身含义的解释
@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public interface BaseEnum {
String desc();
}
然后枚举类的定义:
import lombok.Getter;
@Getter
public enum AnswerStatus implements BaseEnum {
//正确
CORRECT("正确"),
//错误
WRONG("错误"),
//半正确
PARTIAL("半对");
private final String name;
private final String desc;
AnswerStatus(String desc) {
this.name = this.name();
this.desc = desc;
}
@Override
public String desc() {
return this.desc;
}
}
如果写这样一个接口方法:
@GetMapping
public AnswerStatus[] getAnswerStatus() {
return AnswerStatus.values();
}
其查询结果为:
[
{
"name": "CORRECT",
"desc": "正确"
},
{
"name": "WRONG",
"desc": "错误"
},
{
"name": "PARTIAL",
"desc": "半对"
},
{
"name": "UNRECOGNIZED",
"desc": "未识别到"
}
]
可以看到跟不使用这个注解,只有一个名字的查询结果很不一样
引发的问题
一定场景下的反序列化问题
序列化方便了,那么反序列化就会变的麻烦
枚举类的默认反序列化方法是接收枚举对象的名字来创建,显而易见如果你只是接收前端或者数据库(数据库枚举类存储也只是枚举对象的名字时)的数据,那么反序列化不会有任何问题。
但是,在有对象转化或者序列化传递时就会有问题,例如:open Feign中查询结果的反序列化。
因为序列化的是以对象的方式,反序列化会以Object的方式去寻找反序列化方法,结果当然是找不到。
需要在实现了BaseEnum的枚举类下实现自定义的@JsonCreator方法,同时BaseEnum也需要增加一些公共的方法:
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.ObjectMapper;
@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public interface BaseEnum {
String desc();
ObjectMapper objectMapper = new ObjectMapper();
static <E extends Enum<E> & BaseEnum> E fromName(String desc, Class<E> enumClass) {
for (E answerSheetOptionArrangement : enumClass.getEnumConstants()) {
if (answerSheetOptionArrangement.name().equals(desc)) {
return answerSheetOptionArrangement;
}
}
return null;
}
static String getNameValue(String input) {
if (input.startsWith("{") && input.endsWith("}")) {
input = input.substring(1, input.length() - 1);
String[] pairs = input.split(", ");
for (String pair : pairs) {
if (pair.startsWith("name=")) {
return pair.split("=")[1];
}
}
}
return null;
}
}
自定义完整枚举类:
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
@Getter
public enum AnswerStatus implements BaseEnum {
//正确
CORRECT("正确"),
//错误
WRONG("错误"),
//半正确
PARTIAL("半对"),
//未识别
UNRECOGNIZED("未识别到");
private final String name;
private final String desc;
AnswerStatus(String desc) {
this.name = name();
this.desc = desc;
}
@Override
public String desc() {
return this.desc;
}
@JsonCreator(mode = JsonCreator.Mode.DELEGATING)
public static AnswerStatus forNames(@JsonProperty Object object) {
if (object == null) {
return null;
}
String jsonStr = object.toString();
return forNames(jsonStr);
}
public static AnswerStatus forNames(@JsonProperty String jsonStr) {
if (jsonStr == null) {
return null;
}
if (!JSON.isValid(jsonStr)) {
if (jsonStr.contains("name=")) {
return BaseEnum.fromName(BaseEnum.getNameValue(jsonStr), AnswerStatus.class);
}
return BaseEnum.fromName(jsonStr, AnswerStatus.class);
}
try {
JSONObject jsonObject = JSON.parseObject(jsonStr);
if (jsonObject.containsKey("name")) {
return BaseEnum.fromName(jsonObject.getString("name"), AnswerStatus.class);
}
} catch (Exception ignore) {
// ignore
}
return BaseEnum.fromName(jsonStr, AnswerStatus.class);
}
}
很多报错都与反序列化方法考虑不全面导致的,例如:
1
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Invalid type definition for type `xxx.xxx.AnswerStatus`: More than one argument (#0 and #1) left as delegating for Creator [method xxx.xxx.AnswerStatus#forObject(2 params)]: only one allowed
at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 1]
2
Caused by: com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type `java.lang.String` from Object value (token `JsonToken.START_OBJECT`)
at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 117] (through reference chain: java.util.ArrayList[0]->xxx.Object["category"]->xxx.AnswerStatus["superCategory"])
3
Caused by: com.fasterxml.jackson.databind.exc.MismatchedInputException: Input mismatch reading Enum `xxx.AnswerStatus`: properties-based `@JsonCreator` ([method xxx.AnswerStatus#forNames(java.lang.String)]) expects String Value, got Object value (`JsonToken.START_OBJECT`)
at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 77] (through reference chain: xxx.AnswerStatus["pattern"])
4
An unexpected error occurred: Error while extracting response for type [class xxx.AnswerStatus] and content type [application/json;charset=UTF-8]
feign.codec.DecodeException: Error while extracting response for type [class xxx.AnswerStatus] and content type [application/json;charset=UTF-8]
等等
都是@JsonCreator方法的问题