文章目录

  • 一、简要说明
  • 二、相应JAVA类的解释说明
  • 三、需求说明
  • 四、出现的问题
  • 五、总结



一、简要说明

此文章主要针对在实际开发中遇到的需要将枚举集合进行Json序列化的一个问题的处理方案.

二、相应JAVA类的解释说明

//消息配置-响应对象类
public class  MsgConfigListItemVO{

	//本次需要处理的枚举集合(List<Enum>)
    @ApiModelProperty(value = "通知方式列表")
    private List<MsgNotifyMethodEnum> notifyMethods;
}

// {@link MsgNotifyMethodEnum}  枚举类
public enum MsgNotifyMethodEnum implements Dictionary<String, String> {
    WS("WS", "站内信"),
    SMS("SMS", "短信"),
    EMAIL("EMAIL", "邮件");

    private final String code;
    private final String name;
}

//此类为所有枚举类的顶层接口类(贴出来只是为了方便大家看懂代码结构,无实际意义)
public interface Dictionary<C extends Serializable, N> extends IEnum<C> {
    C getCode();
    N getName();
    @Override
    default C getValue() {
        return getCode();
    }
}

三、需求说明

字段(notifyMethods)在Mysql数据库中的类型为(json),存储的格式为[“WS”,“SMS”,“EMAIL”]
需要将字段(notifyMethods)以如下结构返回给前端

{
	notifyMethods:[
		{
		"code":"WS",
		"name:"站内信"
		},
		{
		"code":"SMS",
		"name:"短信"
		},
		{
		"code":"EMAIL",
		"name:"站邮件信"
		}
	]
}

四、出现的问题

如上诉需求,如果要将枚举进行结构的转换,需要使用到Json序列化,也就是( @JsonSerialize(using = DictObjectSerializer.class)注解)
以下是 DictObjectSerializer类的具体实现:

public class DictObjectSerializer extends JsonSerializer<Dictionary> {
    @Override
    public void serialize(Dictionary dictionary, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        jsonGenerator.writeStartObject();
        jsonGenerator.writeStringField("code",String.valueOf(dictionary.getCode()));
        jsonGenerator.writeStringField("name",String.valueOf(dictionary.getName()));
        jsonGenerator.writeEndObject();
    }
}

JsonSerializer<?>可指定泛型,所以正常情况下只需要此代码修改为如下即可完成枚举集合的序列化功能:

public class DictListSerializer extends JsonSerializer<List<Dictionary>> {
    @Override
    public void serialize(List<Dictionary> dictionarylist, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
		List<Map<String,String>> endList=dictionarylist.stream.map(item->{
				Map<String,String> map=new HashMap();
				map.put("code",String.valueOf(item.getCode()));
				map.put("name",String.valueOf(item.getName()));
				return map;
		}).collect(Collectors.toList());
        jsonGenerator.writeObject(endList);
    }
}

//添加注解
public class  MsgConfigListItemVO{
    @ApiModelProperty(value = "通知方式列表")
     @JsonSerialize(using = DictListSerializer.class)
    private List<MsgNotifyMethodEnum> notifyMethods;
}

如上也是我最开始的处理逻辑,但是在调试的过程中发现List中的元素并不是枚举对象(MsgNotifyMethodEnum),
而是枚举字符串(示例:[“WS”,“SMS”,“EMAIL”]),所以也就不可能会有getCode和getName方法,甚至在循环时就直接报错,因为字符串不能强转为Dictionary对象
然后在网上查询了一下,说是因为泛型擦除的问题导致的(关于这个问题大家可自行搜索),所以上述代码逻辑并不能实现枚举集合的序列化,于是我使用自定义注解跟@JsonSerialize相结合完成了枚举集合的序列化逻辑(经过验证可以正常使用,并且已经在项目中正式使用),如下:

注意:由于本项目中的所有枚举类均实现了顶层接口Dictionary,所以在相关地方进行了限制,大家可根据实际情况自行修改.

/**
 * @author zhanglei
 * @version 1.0.0
 * @serial 2023/5/17
 * @apiNote 自定义序列化注解, 用于实现List<Enum>的序列化功能
 */
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside

//(DictListSerializer)为序列化的具体实现类
@JsonSerialize(using = DictListSerializer.class)

@SuppressWarnings("rawtypes")
public @interface JsonListSerialize {

    /**
     * 枚举类的.class
     * 注意:需要实现Dictionary类的枚举类才可以使用这个注解
     * 示例: @JsonListSerialize(enumClass = MsgNotifyMethodEnum.class),表示将:List<MsgNotifyMethodEnum>(["SMS","MSG"]) 序列化为:[{"code": "SMS", "name": "短信"},{"code": "MSG", "name": "站内信"}]
     *
     * @return Dictionary
     */
    Class<? extends Dictionary> enumClass();

}
/**
 * @author zhanglei
 * @version 1.0.0
 * @serial 2023/5/16
 * @apiNote 自定义 List<Enum> 类的序列化类
 */
 //注意,需要添加这个注解,不然将没有无参构造方法(或者也可不使用注解,自定义无参构造方法)
@NoArgsConstructor
@SuppressWarnings("rawtypes")
public class DictListSerializer extends JsonSerializer<List<Object>> implements ContextualSerializer {

    private JsonListSerialize jsonListSerialize;

    public DictListSerializer(JsonListSerialize jsonListSerialize) {
        this.jsonListSerialize = jsonListSerialize;
    }

    @Override
    public void serialize(List<Object> dictionaryObjectList, JsonGenerator jsonGenerator, SerializerProvider serializers) throws IOException {
        if (ObjectUtil.isNull(jsonListSerialize)) {
            jsonGenerator.writeObject(Collections.emptyList());
            return;
        }

        Class<? extends Dictionary> enumClass = jsonListSerialize.enumClass();
        if (ObjectUtil.isNull(enumClass)) {
            jsonGenerator.writeObject(Collections.emptyList());
            return;
        }

        Map<Object, ? extends Dictionary> dictEnumMap = Arrays.stream(enumClass.getEnumConstants()).collect(Collectors.toMap(Dictionary::getCode, Function.identity()));

        List<ResultData> endList = dictionaryObjectList.stream().map(item -> {
            ResultData.ResultDataBuilder builder = ResultData.initialBuilder(item);

            /*
            如果需要序列化的类本身就是<Dictionary>的子枚举类,则直接获取对应的属性,然后返回
            具体说明:
                为什么这里需要判断一次是否为枚举对象?
                因为在实践中发现,如果在数据库中的字段类型为json,并且保存的为枚举类型的集合(例如:["A","B"]),那么查询出来的数据类型实际上为枚举字符串,并非枚举对象
                而如果直接使用代码中定义的枚举对象(例如:根据key获取到枚举集合),那么获取到的数据类型实际上就是枚举对象
                所以如果是枚举对象,则不需要从dictEnumMap中获再获取一次枚举对象,直接返回即可,反之则需要根据枚举字符串(也就是枚举code)从dictEnumMap中获取到对应的枚举对象
            */
            if (Dictionary.class.isAssignableFrom(item.getClass())) {
                Dictionary dictionary = (Dictionary) item;
                return builder.code(String.valueOf(dictionary.getCode())).name(String.valueOf(dictionary.getName())).build();
            }

            //根据枚举的code获取对应的枚举值
            Dictionary dictionary = dictEnumMap.get(item);
            if (ObjectUtil.isNotNull(dictionary)) {
                return builder.code(String.valueOf(dictionary.getCode())).name(String.valueOf(dictionary.getName())).build();
            }

            //返回兜底数据以便可以直观的发现出枚举值定义的问题
            return builder.build();
        }).collect(Collectors.toList());

        jsonGenerator.writeObject(endList);
    }

   	// 这个方法如果有疑问大家自行百度
    @Override
    public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
        if (property != null) {
            JsonListSerialize annotation = property.getAnnotation(JsonListSerialize.class);
            if (annotation != null) {
            	/*
				这里则进行对象(DictListSerializer)的实例化,使用的是有参构造器,
				不一定非要返回@JsonListSerialize对象,也可以在这里进行枚举类的处理,将枚举Map返回,
				大家自行根据自己的爱好和编码风格修改
				*/
                return new DictListSerializer(annotation);
            }
            return prov.findValueSerializer(property.getType(), property);
        }
        return prov.findNullValueSerializer(null);
    }

    @Data
    @Builder
    @AllArgsConstructor
    @NoArgsConstructor
    private static class ResultData {

        @ApiModelProperty("code")
        private String code;

        @ApiModelProperty("name")
        private String name;

        public static ResultData.ResultDataBuilder initialBuilder(Object item) {
            return ResultData.builder().code(String.valueOf(item)).name("错误的code:" + item);
        }
    }
}

五、总结

以上为枚举集合序列化的处理逻辑,可能还有别的处理方法,这里只是提供一种解决方法或者实思路,供大家参考.