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;
    }

返回结果:

相关推荐
MacroZheng1 小时前
还在用WebSocket实现即时通讯?试试MQTT吧,真香!
java·spring boot·后端
midsummer_woo2 小时前
基于springboot的IT技术交流和分享平台的设计与实现(源码+论文)
java·spring boot·后端
别惹CC3 小时前
Spring AI 进阶之路01:三步将 AI 整合进 Spring Boot
人工智能·spring boot·spring
柯南二号4 小时前
【Java后端】Spring Boot 集成 MyBatis-Plus 全攻略
java·spring boot·mybatis
javachen__5 小时前
SpringBoot整合P6Spy实现全链路SQL监控
spring boot·后端·sql
IT毕设实战小研11 小时前
基于Spring Boot 4s店车辆管理系统 租车管理系统 停车位管理系统 智慧车辆管理系统
java·开发语言·spring boot·后端·spring·毕业设计·课程设计
一只爱撸猫的程序猿12 小时前
使用Spring AI配合MCP(Model Context Protocol)构建一个"智能代码审查助手"
spring boot·aigc·ai编程
甄超锋13 小时前
Java ArrayList的介绍及用法
java·windows·spring boot·python·spring·spring cloud·tomcat
武昌库里写JAVA15 小时前
JAVA面试汇总(四)JVM(一)
java·vue.js·spring boot·sql·学习
Pitayafruit16 小时前
Spring AI 进阶之路03:集成RAG构建高效知识库
spring boot·后端·llm