Jackson 序列化字典字段属性

前言

在前后端分离的项目中,存在很多字段是下拉框选项的情况。 比如:性别字段

代码值value 显示值label
1
0
2 其他
在数据库中,存的是代码值,前端显示的时候,需要显示值。这时候,在返回的时候,就需要做一次代码转换。
(为什么不是前端自己转换?为了统一管理和方便变更。
# 场景
  • 字典字段为空,则不转换。
  • 一个对象中的多个字段是字典字段。
  • 对象中的子对象也有字典字段。
  • 对象的子对象是集合,也存在字典字段。

正文

因为在业务开发过程中,存在非常多的这种实体,所以需要制定一个统一处理。减少业务开发的工作量。 (最终结果,减少到只需要一行注解)

升级版连接 Jackson 序列号字典字段属性(升级)

一 添加配置

项目中都有 fasterxml 的依赖吧,没有自己添加。 添加类 JacksonConfig,用于配置自定义内省器。

import com.cah.project.conf.serializer.ProjectJacksonAnnotationIntrospector;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;

/**
 * 功能描述: Jackson配置 <br/>
 */
@Configuration
public class JacksonConfig {

    public JacksonConfig() {}

    @Bean
    @Primary
    @ConditionalOnMissingBean({ObjectMapper.class})
    public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
        ObjectMapper objectMapper = builder.createXmlMapper(false).build();
        // 定义 不为空,才能进行序列化
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        // 添加 自定义 项目注解内省器
        objectMapper.setAnnotationIntrospector(new ProjectJacksonAnnotationIntrospector());
        return objectMapper;
    }

}
复制代码

二 添加项目内省器

import com.cah.project.core.annotation.Dict;
import com.fasterxml.jackson.databind.introspect.Annotated;
import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;

/**
 * 功能描述: 自定义项目内神器 <br/>
 */
public class ProjectJacksonAnnotationIntrospector extends JacksonAnnotationIntrospector {

    public ProjectJacksonAnnotationIntrospector() {}

    @Override
    public Object findSerializer(Annotated a) {
        // 如果是字典注解
        Dict dict = _findAnnotation(a, Dict.class);
        if(dict != null) {
            return new DictSerializer(dict.type(), dict.separator());
        }
        // 其他扩展。。。
        return super.findSerializer(a);
    }

}
复制代码

其中 Dict 注解类 为自定义注解。内容如下:


import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 功能描述: 字典注解 <br/>
 */
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Dict {

    /** 字典类型 */
    String type();
    /** 字典类型描述 */
    String desc() default "";
    /** 如果多个字典拼接,自定义分隔符 */
    String separator() default ",";

}
复制代码

三 自定义序列化规则

import com.cah.project.core.cache.DictCacheUtil;
import com.cah.project.core.domain.bo.DictData;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;

import java.io.IOException;

/**
 * 功能描述: 字典序列化 <br/>
 */
public class DictSerializer extends StdSerializer<Object> {

    /** 字典类型 */
    private final String type;
    /** 分隔符 */
    private final String separator;

    public DictSerializer(String type, String separator) {
        super(Object.class);
        this.type = type;
        this.separator = separator;
    }

    @Override
    public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException {
        gen.writeObject(this.getLabel(value.toString()));
    }

    /**
     * 功能描述: 核心逻辑 <br/>
     * 将码值 转换成 显示对象
     * @param code 数据内容
     * @return "com.cah.project.core.domain.out.DictData"
     */
    private DictData getLabel(String code) {
        DictData dd = new DictData();
        dd.setType(this.type);
        dd.setValue(code);
        if(this.separator != null && this.separator.length() > 0) {
            String[] strs = code.split(this.separator);
            StringBuilder sb = new StringBuilder();
            for(String str : strs) {
                // 从缓存中获取字典。如果不行,通过SpringUtil.getBean(); 获取服务处理
                sb.append(DictCacheUtil.getLabel(this.type, str)).append(this.separator);
            }
            dd.setLabel(sb.substring(0, sb.length() - this.separator.length() + 1));
        } else {
            // 从缓存中获取字典。如果不行,通过SpringUtil.getBean(); 获取服务处理
            dd.setLabel(DictCacheUtil.getLabel(this.type, code));
        }
        return dd;
    }

}

复制代码

四 字典缓存工具

字典缓存工具,使用的是hutool工具包中的缓存。 至于缓存如何清理与同步,这是另外一个问题。也可以从redis中获取缓存。 缓存是为了加快转换速度。各自发挥就好了。

import cn.hutool.cache.Cache;
import cn.hutool.cache.CacheUtil;
import com.cah.project.core.domain.bo.DictData;

import java.util.HashMap;
import java.util.Map;

/**
 * 功能描述: 字典缓存工具 <br/>
 */
public class DictCacheUtil {

    /** 字典缓存,最外层key为 字典的type,第二层缓存的key为字典的value */
    protected static Cache<String, Map<String, DictData>> dictCache = CacheUtil.newLFUCache(10000);

    /**
     * 功能描述: 添加缓存 <br/>
     *
     * @param type 字典类型
     * @param dd 字典对象
     */
    public static void put(String type, DictData dd) {
        Map<String, DictData> map = dictCache.get(type);
        if(map == null) {
            map = new HashMap<>();
            dictCache.put(type, map);
        }
        map.put(dd.getValue(), dd);
    }

    /**
     * 功能描述: 获取字典对象 <br/>
     *
     * @param type 字典类型
     * @param value 字典码值
     * @return "com.cah.project.core.domain.bo.DictData"
     */
    public static DictData get(String type, String value) {
        Map<String, DictData> map = dictCache.get(type);
        if(map == null) {
            return new DictData(type, value, value);
        }
        return map.getOrDefault(value, new DictData(type, value, value));
    }

    /**
     * 功能描述: 获取显示值 <br/>
     *
     * @param type 字典类型
     * @param value 字典码值
     * @return "java.lang.String"
     */
    public static String getLabel(String type, String value) {
        Map<String, DictData> map = dictCache.get(type);
        if(map == null) {
            return value;
        }
        DictData dd = map.get(value);
        return dd != null ? dd.getLabel() : value;
    }

}

复制代码

五 测试对象

测试对象为返回的实体对象。其中字段字段都添加了 @Dict 注解

import com.cah.project.core.annotation.Dict;
import lombok.Data;

import java.io.Serializable;
import java.util.List;

@Data
public class TestOutVO implements Serializable {

    private String name;

    @Dict(type = "STATUS_CD")
    private String status;

    @Dict(type = "SEX_CD")
    private String sex;

    private List<ChildOutVO> children;

}
复制代码
import com.cah.project.core.annotation.Dict;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ChildOutVO {

    private String name;

    @Dict(type = "TYPE_CD")
    private Integer type;

    @Dict(type = "SEX_CD")
    private String sex;

}
复制代码

六 测试接口

import cn.hutool.core.collection.ListUtil;
import com.cah.project.core.domain.out.CommonResult;
import com.cah.project.test.out.ChildOutVO;
import com.cah.project.test.out.TestOutVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Lazy;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * <p>测试 控制层 </p> <br/>
 *
 * @author cah
 * @since 2021-10-06
 */
@Api(tags = {"测试 控制层"})
@RestController
@RequestMapping("/test")
@RequiredArgsConstructor(onConstructor_ = {@Lazy})
public class TestController {

    @GetMapping("getById")
    @ApiOperation(value="测试获取数据")
    public CommonResult<TestOutVO> getById(@RequestParam("id") String id){
        TestOutVO outVO = new TestOutVO();
        outVO.setName("名称");
        outVO.setStatus("1");
        outVO.setSex("0,1");
        ChildOutVO c1 = ChildOutVO.builder().name("子1").type(2).sex("0").build();
        ChildOutVO c2 = ChildOutVO.builder().name("子2").type(3).sex("1").build();
        List<ChildOutVO> list = ListUtil.toList(c1, c2);
        outVO.setChildren(list);
        return CommonResult.data(outVO);
    }

}
复制代码

七 测试结果

在这里插入图片描述

总结

通过以上的方式,可以解放业务开发过程中反复的字典映射操作。但是也存在一些问题。比如复杂的转换,非缓存数据转换等。 对于以上问题,可以使用 @JsonSerialize(using = XxxSerializer.class) 的方式进行处理。

代码地址

猜你喜欢

转载自juejin.im/post/7069638423191126053