背景
公司项目中使用的fastjson版本是1.2.68,该版本对枚举的序列化和反序列都是根据枚举name来实现的,而实际对外提供的接口中都是定义code值,例如1,2,3,4这样的。如果要实现这个效果要么升级到fastjson2、或者用jackson,但是考虑到是线上项目,升级存在风险比较大,所有改用fastjson自定义序列化和反序列化。
解决方法
1. 定义一个通用枚举接口
public interface MyEnum {
/*
* 获取code值方法
*/
int getCode();
}
2. 自定义枚举序列化和反序列
public class MyEnumCodec implements ObjectSerializer, ObjectDeserializer {
/**
* 反序列化
*/
@Override
public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features) throws IOException {
if (object == null){
serializer.out.writeNull();
} else {
MyEnum myEnum = (MyEnum) object;
//序列化时调用getCode方法
serializer.out.writeInt(myEnum.getCode());
}
}
/**
* 反序列化
*/
@Override
public <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName) {
final JSONLexer lexer = parser.lexer;
final int token = lexer.token();
Class cls = (Class) type;
//获取当前枚举的所有枚举值
Object[] enumConstants = cls.getEnumConstants();
//判断class是不是MyEnum的实现类
if (MyEnum.class.isAssignableFrom(cls)) {
for (Object enumConstant : enumConstants) {
//如果getCode返回值等于json字段的值,返回这个枚举值
if (((MyEnum) enumConstant).getCode() == lexer.intValue()) {
return (T) enumConstant;
}
}
} else {
//没实现EnumValue接口的 默认的按名字或者按ordinal
if (token == JSONToken.LITERAL_INT) {
int intValue = lexer.intValue();
lexer.nextToken(JSONToken.COMMA);
if (intValue < 0 || intValue > enumConstants.length) {
throw new JSONException("parse enum " + cls.getName() + " error, value : " + intValue);
}
return (T) enumConstants[intValue];
} else if (token == JSONToken.LITERAL_STRING) {
return (T) Enum.valueOf(cls, lexer.stringVal());
}
}
return null;
}
@Override
public int getFastMatchToken() {
return JSONToken.LITERAL_INT;
}
}
3. 自定义枚举类
public interface MySelfEnums {
//这里用到了lombok 默认实现了getCode方法
@Getter
enum Delete implements MyEnum{
/**
* 停用
*/
NO_DELETE(0,"否"),
/**
* 启用
*/
DELETED(1,"是");
private int code;
private String desc;
Delete(Integer code, String desc) {
this.code = code;
this.desc = desc;
}
}
}
4.声明自定义序列化
/**
* 用fastjson注解声明
*/
@JSONField(serializeUsing = MyEnumCodec.class, deserializeUsing = MyEnumCodec.class)
private MySelfEnums.Delete delete;
考虑到项目中这种自定义序列化和反序列的枚举很多,使用全局配置比较轻松
//获取所有实现MyEnum的子类 这里使用了Hutool工具类
Set<Class<?>> classSet = ClassUtil.scanPackageBySuper("com.mambike.sales", MyEnum.class);
for (Class<?> aClass : classSet) {
//判断是否为枚举
if (ClassUtil.isEnum(aClass)){
//fastjson只有拿到具体类才能配置序列化和反序列成功
//添加序列化
SerializeConfig.getGlobalInstance().put(aClass, new MyEnumCodec());
//添加反序列化
ParserConfig.getGlobalInstance().putDeserializer(aClass, new MyEnumCodec());
}
}
项目使用的是springboot,这段代码只要在springboot启用之前执行就可以。
总结
这次实现自定义枚举序列化和反序列参考很多网上其他作者的资料,我只是稍微添加一下自己的思考。希望给遇到这类问题的小伙伴提供一个借鉴的思路。我也只是稍微测试了一下,项目中没什么问题,如果有什么不足之处还望指教。