Jackson 2.x 系列【31】Spring Boot 集成之字典回写

有道无术,术尚可求,有术无道,止于术。

本系列Jackson 版本 2.17.0

本系列Spring Boot 版本 3.2.4

源码地址:https://gitee.com/pearl-organization/study-jaskson-demo

文章目录

    • [1. 场景描述](#1. 场景描述)
    • [2. 案例演示](#2. 案例演示)
      • [2.1 修改枚举](#2.1 修改枚举)
      • [2.2 定义注解](#2.2 定义注解)
      • [2.3 自定义序列化器](#2.3 自定义序列化器)
      • [2.4 自定义装饰器](#2.4 自定义装饰器)
      • [2.5 配置](#2.5 配置)
      • [2.6 测试](#2.6 测试)

1. 场景描述

例如,用户对象中的性别字段,一般在数据库都是使用数值表示,枚举类如下:

java 复制代码
public enum GenderEnum {

    MAN(1, "男"),

    WOMAN(2, "女");

    GenderEnum(int code, String desc) {
        this.code = code;
        this.desc = desc;
    }

    private int code;

    private String desc;

    public static String getDesc(int code) {
        for (GenderEnum c : GenderEnum.values()) {
            if (c.getCode() == code) {
                return c.getDesc();
            }
        }
        return null;
    }

    public int getCode() {
        return code;
    }

    public String getDesc() {
        return desc;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }
}

用户对象如下:

java 复制代码
@Data
@ToString
public class PersonVO implements Serializable {

    Long id;

    String username;

    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    Date birthday;

    Integer gender;

返回前端示例如下:

json 复制代码
 {
 "id": "1699657986705854464",
 "username": "jack",
 "birthday": "2024-04-23 14:21:54",
 "gender": 1
 }

这时,需要将数值类型翻译为对应的描述,例如性别1应该翻译为 。和上篇的数据脱敏一样,后端可以在数据库查询或者反序列化返回Http响应时进行处理。

2. 案例演示

演示需求:将性别编码值翻译后,返回给前端。

示例:

json 复制代码
 {
 "id": "1699657986705854464",
 "username": "jack",
 "birthday": "2024-04-23 14:21:54",
 "gender": 1,
 "genderText":"男"
 }

2.1 修改枚举

定义一个公共的枚举接口,定义一个根据编码值获取对应描述的方法:

java 复制代码
public interface CommonEnum {

    /**
     * 根据编码值获取描述
     */
    String getDescription(int code);
}

GenderEnum 实现公共枚举接口,并实现其方法,这里枚举类相当于一本字典,根据编码值可以翻译为描述字符串,实际开发时,也可以使用数据库或者缓存,建立专门的字典表进行维护:

java 复制代码
public enum GenderEnum implements CommonEnum {

    MAN(1, "男"),

    WOMAN(2, "女");

    GenderEnum(int code, String desc) {
        this.code = code;
        this.desc = desc;
    }

    private int code;

    private String desc;

    @Override
    public String getDescription(int code) {
        for (GenderEnum c : GenderEnum.values()) {
            if (c.getCode() == code) {
                return c.getDesc();
            }
        }
        return "";
    }

    public int getCode() {
        return code;
    }

    public String getDesc() {
        return desc;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }
}

2.2 定义注解

定义一个字典注解,这里使用枚举类作为字典:

java 复制代码
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@JacksonAnnotationsInside
public @interface Dict {

    /**
     * 指定后缀
     * 翻译后的属性名称:添加了注解的属性名+指定后缀
     */
    String suffix() default "Text";

    /**
     * 字典使用枚举(实际开发可以使用数据库或者缓存)
     */
    Class<? extends CommonEnum> using();
}

2.3 自定义序列化器

自定义序列化器执行翻译写出操作:

java 复制代码
public class DictJsonSerializer extends StdSerializer<Object> {

    private CommonEnum commonEnum;

    protected DictJsonSerializer() {
        super(Object.class);
    }

    protected DictJsonSerializer( CommonEnum commonEnum) {
        super(Object.class);
        this.commonEnum = commonEnum;
    }

    /**
     * 序列化
     */
    @Override
    public void serialize(Object code, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        // 获取枚举对应的翻译值并写出
        String description =commonEnum.getDescription(Integer.parseInt(code.toString()));
        jsonGenerator.writeString(description);
    }
}

2.4 自定义装饰器

自定义Bean对象序列化装饰器,解析添加了@Dict注解的属性,据此新建一个虚拟属性:

java 复制代码
public class DictBeanSerializerModifier extends BeanSerializerModifier {

    public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc, List<BeanPropertyWriter> beanProperties) {
        // 1. 创建新的属性集合
        List<BeanPropertyWriter> newBeanProperties = CollUtil.newArrayList(beanProperties);
        // 2. 循环所有属性
        for (BeanPropertyWriter propertyWriter : beanProperties) {
            // 3. 获取注解 @Dict
            Dict annotation = propertyWriter.getAnnotation(Dict.class);
            if (annotation == null) {
                annotation = propertyWriter.getContextAnnotation(Dict.class);
            }
            if (annotation != null) {
                // 4. 新建一个虚拟属性(名称为:添加了注解的属性名+指定后缀)
                NameTransformer transformer = NameTransformer.simpleTransformer("", annotation.suffix());
                BeanPropertyWriter newProperty = propertyWriter.rename(transformer);
                CommonEnum commonEnum = null;
                Class<? extends CommonEnum> using = annotation.using();
                // 5. 获取枚举示例(无法反射,所以直接 IF 判断)
                if (GenderEnum.class.isAssignableFrom(using)) {
                    commonEnum = GenderEnum.MAN;
                }
                // 6. 设置虚拟属性的序列化器
                newProperty.assignSerializer(new DictJsonSerializer(commonEnum));
                newBeanProperties.add(newProperty);
            }
        }
        return newBeanProperties;
    }
}

2.5 配置

添加Spring配置类,注册自定义的ObjectMapper,并设置BeanSerializerModifier

java 复制代码
@Configuration
public class ObjectMapperConfig {

    @Bean
    @Primary
    ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
        ObjectMapper objectMapper = builder.createXmlMapper(false).build();
        SerializerFactory serializerFactory = objectMapper
                .getSerializerFactory()
                .withSerializerModifier(new DictBeanSerializerModifier());
        objectMapper.setSerializerFactory(serializerFactory);
        return objectMapper;
    }
}

2.6 测试

用户类添加翻译注解:

java 复制代码
@Data
@ToString
public class PersonVO implements Serializable {

    Long id;

    String username;

    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    Date birthday;

    @Dict(using = GenderEnum.class)
    Integer gender;

访问测试接口:

java 复制代码
    @RequestMapping("/test")
    public PersonVO test() {
        PersonVO vo = new PersonVO();
        vo.setId(1699657986705854464L);
        vo.setUsername("jack");
        vo.setBirthday(new Date());
        vo.setGender(2);
        return vo;
    }

返回结果:

相关推荐
FIN技术铺6 分钟前
Spring Boot框架Starter组件整理
java·spring boot·后端
小码的头发丝、31 分钟前
Spring Boot 注解
java·spring boot
午觉千万别睡过34 分钟前
RuoYI分页不准确问题解决
spring boot
2301_811274311 小时前
大数据基于Spring Boot的化妆品推荐系统的设计与实现
大数据·spring boot·后端
编程重生之路2 小时前
Springboot启动异常 错误: 找不到或无法加载主类 xxx.Application异常
java·spring boot·后端
politeboy2 小时前
k8s启动springboot容器的时候,显示找不到application.yml文件
java·spring boot·kubernetes
世间万物皆对象8 小时前
Spring Boot核心概念:日志管理
java·spring boot·单元测试
qq_174482857510 小时前
springboot基于微信小程序的旧衣回收系统的设计与实现
spring boot·后端·微信小程序
代码小鑫12 小时前
A043-基于Spring Boot的秒杀系统设计与实现
java·开发语言·数据库·spring boot·后端·spring·毕业设计
真心喜欢你吖12 小时前
SpringBoot与MongoDB深度整合及应用案例
java·spring boot·后端·mongodb·spring