Jackson替换fastjson 且用Jackson实现动态过滤属性字段

开发板推荐:天空星STM32F407VET6开发板

超高性价比 STM32主控 | 超高主频 | 一板兼容百芯 | 比赛神器 | 沉金彩色丝印

1.背景

        为了适配国产化,需对现有项目进行改造升级,由原来的EJB框架升级为spring-boot。

        在升级过程中,发现fastjson2 源码有些小bug,在序列化对象时,对于深度嵌套的对象结构,fastjson2 可能会出现意外的行为,如丢失部分数据或生成不符合预期的 JSON 结构。

        故而打算将fastjson全部替换为使用jackson,其中需要注意的是对象的属性过滤,也就是fastjson出现的bug。在jackson工具类中进行了自定义过滤器和方法封装,封装的方法名类似fastjson2,方便对fastjson2进行快速替换,且能实现深度嵌套对象的正确属性过滤

2.动态过滤对象属性

2.1 网上常见方法

        jackson中对象的属性字段过滤有很多种方法,包括注解、自定义序列化器和反序列化器、ObjectMapper 配置、Mixin 注解以及基于视图的过滤等,网上大多是使用Mixin注解,如:

public abstract class UserMixin {
    @JsonIgnore
    private String password;
}

ObjectMapper mapper = new ObjectMapper();
mapper.addMixIn(User.class, UserMixin.class);

        如果需要过滤User类,需要创建对应的UserMixin类
        注:@JsonIgnore:完全忽略指定字段,使其不参与序列化和反序列化,通过 Mixin 注解类将注解应用到现有类上,而无需修改原始类。

2.2  jackson封装的动态过滤对象属性

        上文中的Mixin 注解类方法的问题是每个类都要创建一个对应的Mixin类,比较繁琐。

        基于jackson中Mixin的强大机制,在jackson封装的工具类中,封装了一个动态过滤的方法,需要增加一个JacksonFilter过滤器类,核心就是jackson中的addMixin是可以给一个类附加上去一个过滤器类的。该方法解决了每次过滤都要创建对应的Mixin 注解类的问题。

2.2.1 封装的jackson工具类

package com.test;

import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;

import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

public class JsonUtil {

    public static final ObjectMapper mapper = new ObjectMapper();

    static {
        // 配置序列化功能,当序列化空对象时不会抛出异常
        mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
        // 设置序列化方式,只序列化非空字段
        mapper.setSerializationInclusion(Include.NON_NULL);
        // 启用JSON 输出时的缩进格式
        mapper.enable(SerializationFeature.INDENT_OUTPUT);
        // 配置反序列化功能,忽略JSON中未知的属性
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    }

    /**
     * 将 JSON 字符串转换为指定类型的对象
     *
     * @param jsonString JSON 字符串
     * @param clazz      目标对象的 Class 类型
     * @param <T>        目标对象的类型
     * @return 转换后的对象,如果转换失败则返回 null
     */
    public static <T> T parseObject(String jsonString, Class<T> clazz) {
        try {
            return mapper.readValue(jsonString, clazz);
        } catch (IOException e) {
            Logger.getLogger(JsonUtil.class.getName()).log(Level.SEVERE, "Failed to parse JSON string to object", e);
            return null;
        }
    }

    /**
     * 将 JSON 字符串转换为指定类型的对象
     *
     * @param jsonString JSON 字符串
     * @param typeReference 目标对象的 TypeReference 类型
     * @param <T> 目标对象的类型
     * @return 转换后的对象,如果转换失败则返回 null
     */
    public static <T> T parseObject(String jsonString, TypeReference<T> typeReference) {
        try {
            return mapper.readValue(jsonString, typeReference);
        } catch (IOException e) {
            Logger.getLogger(JsonUtil.class.getName()).log(Level.SEVERE, "Failed to parse JSON string to object<T>", e);
            return null;
        }
    }

    /**
     * 将 JSON 字符串转换为 List 对象
     *
     * @param jsonString JSON 字符串
     * @param clazz      List 中元素的 Class 类型
     * @param <T>        List 中元素的类型
     * @return 转换后的 List 对象,如果转换失败则返回 null
     */
    public static <T> List<T> parseArray(String jsonString, Class<T> clazz) {
        try {
            return mapper.readValue(jsonString, mapper.getTypeFactory().constructCollectionType(List.class, clazz));
        } catch (IOException e) {
            Logger.getLogger(JsonUtil.class.getName()).log(Level.SEVERE, "Failed to parse JSON array to list", e);
            return null;
        }
    }

    /**
     * 将 JSON 字符串转换为 List 对象
     *
     * @param jsonString JSON 字符串
     * @param typeReference 目标对象的 TypeReference 类型
     * @param <T> 目标对象的类型
     * @return 转换后的 List 对象,如果转换失败则返回 null
     */
    public static <T> List<T> parseArray(String jsonString, TypeReference<List<T>> typeReference) {
        try {
            return mapper.readValue(jsonString, typeReference);
        } catch (IOException e) {
            Logger.getLogger(JsonUtil.class.getName()).log(Level.SEVERE, "Failed to parse JSON array to list<T>", e);
            return null;
        }
    }

    /**
     * 将对象转换为 JSON 字符串
     *
     * @param obj 要转换的对象
     * @return JSON 字符串,如果转换失败则返回空字符串
     */
    public static String toJSONString(Object obj) {
        try {
            return mapper.writeValueAsString(obj);
        } catch (IOException e) {
            Logger.getLogger(JsonUtil.class.getName()).log(Level.SEVERE, "Failed to convert object to JSON string", e);
            return "";
        }
    }

    /**
     * 将对象转换为JSON字符串,应用自定义过滤器,通过包含或过滤对象属性来控制序列化的过程
     *
     * @param obj 要转换为JSON字符串的对象,不能为空
     * @param includeMap 用于指定要包含(保留)的属性,键是类,值是要包含的属性数组
     * @param filterMap 用于指定要过滤(排除)的属性,键是类,值是要过滤的属性数组
     * @return 对象的JSON字符串
     */
    public static String toJSONString(Object obj, Map<Class<?>, String[]> includeMap, Map<Class<?>, String[]> filterMap) {
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
        JacksonFilter jacksonFilter = new JacksonFilter();
        if (obj == null) {
            throw new IllegalArgumentException("Object cannot be null");
        }
        // 设置过滤配置
        if (includeMap != null && !includeMap.isEmpty()) {
            for (Map.Entry<Class<?>, String[]> entry : includeMap.entrySet()) {
                jacksonFilter.include(entry.getKey(), entry.getValue());
                // 应用 Mixin,使 Jackson 使用 JacksonFilter 的过滤逻辑
                objectMapper.addMixIn(entry.getKey(), jacksonFilter.getClass());
            }
        }
        if (filterMap != null && !filterMap.isEmpty()) {
            for (Map.Entry<Class<?>, String[]> entry : filterMap.entrySet()) {
                jacksonFilter.filter(entry.getKey(), entry.getValue());
                objectMapper.addMixIn(entry.getKey(), jacksonFilter.getClass());
            }
        }
        // 设置过滤器
        objectMapper.setFilterProvider(jacksonFilter);
        try {
            return objectMapper.writeValueAsString(obj);
        } catch (Exception e) {
            throw new RuntimeException("转换json字符失败!", e);
        }
    }

    public static void writeValueToStream(OutputStream os, Object obj) {
        try {
            mapper.writeValue(os, obj);
        } catch (IOException e) {
            Logger.getLogger(JsonUtil.class.getName()).log(Level.SEVERE, "Failed to convert object to stream", e);
        }
    }

    public static byte[] writeValueAsBytes(Object obj) {
        try {
            return mapper.writeValueAsBytes(obj);
        } catch (IOException e) {
            Logger.getLogger(JsonUtil.class.getName()).log(Level.SEVERE, "Failed to convert object to byte", e);
            return new byte[0];
        }
    }

    public static JsonNode getPropertyNode(JsonNode node, String field) {
        JsonNode result = null;
        if ((node != null) && (!node.isNull())) {
            if (field.contains(".")) {
                int index = field.indexOf(".") + 1;
                String part = field.substring(0, index - 1);
                String nextField = field.substring(index);
                result = getPropertyNode(node.get(part), nextField);
            } else {
                result = node.get(field);
            }
        }
        return result;
    }

    public static String getText(JsonNode node, String field, String defaultValue) {
        String result = defaultValue;
        if (!node.isNull()) {
            JsonNode fieldNode = getPropertyNode(node, field);
            if ((fieldNode != null) && (!fieldNode.isNull())) {
                result = fieldNode.asText();
            }
        }
        return result;
    }

    public static ObjectNode createObjectNode() {
        return mapper.createObjectNode();
    }

    /**
     * 将给定的对象转换为ObjectNode对象
     * 用于序列化一个Java对象为JSON对象,并将其封装在ObjectNode中
     *
     * @param pojo 要转换为ObjectNode的Java对象
     * @return 转换后的ObjectNode对象,如果发生错误则返回null
     */
    public static ObjectNode createObjectNode(Object pojo) {
        if (pojo == null) {
            throw new IllegalArgumentException("Pojo can not be null.");
        } else {
            try {
                ObjectNode objectNode = createObjectNode();
                JsonParser jsonParser = mapper.getFactory().createParser(writeValueAsBytes(pojo));
                JsonNode jsonNode = (JsonNode) jsonParser.readValueAsTree();
                if (!jsonNode.isObject()) {
                    throw new RuntimeException("JsonNode [" + jsonNode + "] is not a object.");
                } else {
                    objectNode.setAll((ObjectNode) jsonNode);
                    return objectNode;
                }
            } catch (IOException e) {
                Logger.getLogger(JsonUtil.class.getName()).log(Level.SEVERE, "Failed to convert JsonNode to objectNode", e);
                return null;
            }
        }
    }

    public static ArrayNode createArrayNode() {
        return mapper.createArrayNode();
    }

    public static JsonNode toJsonNode(Object obj) {
        try {
            return mapper.valueToTree(obj);
        } catch (Exception e) {
            Logger.getLogger(JsonUtil.class.getName()).log(Level.SEVERE, "Failed to convert object to JsonNode", e);
            return null;
        }
    }
}
        2.2.1.1  工具类中createArrayNode方法说明:

                其中createArrayNode是对应fastjson中的JSONArray,即用来将

        JSONArray arry= new JSONArray();

                替换为:

        ArrayNode arry= JsonUtil.createArrayNode();
        2.2.1.2  工具类中createObjectNode方法说明:

                其中createObjectNode是对应fastjson中的JSONObject,即用来将

        JSONObject jsonObject = new JSONObject();

                替换为:

        ObjectNode arry= JsonUtil.createObjectNode();
        2.2.1.3  工具类中toJsonNode方法说明:

                在创建ObjectNode时,ObjectNode 本身不能直接存放 Java 对象,只能存放基本类型的值(如字符串、数字等)或其它 JsonNode 类型的对象,因此需要先将 Java 对象转换为 Jackson 的 JsonNode 类型,如:

TbFile tbFile = new TbFile();
tbFile.setId("1111");
tbFile.setName("文件");

ObjectNode briefNode = JsonUtil.createObjectNode();
briefNode.put("title","标题");
//存放对象用.set
briefNode.set("file", JsonUtil.toJsonNode(tbFile));

                注:ObjectNode对应fastjson中的JSONObject

2.2.2  JacksonFilter过滤器类

package com.test;

import com.fasterxml.jackson.annotation.JsonFilter;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.BeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.FilterProvider;
import com.fasterxml.jackson.databind.ser.PropertyFilter;
import com.fasterxml.jackson.databind.ser.PropertyWriter;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;

import java.util.*;

@SuppressWarnings("deprecation")
@JsonFilter("JacksonFilter")
public class JacksonFilter  extends FilterProvider {
    Map<Class<?>, Set<String>> includeMap = new HashMap<>();
    Map<Class<?>, Set<String>> filterMap = new HashMap<>();

    public void include(Class<?> type, String[] fields) {
        addToMap(includeMap, type, fields);
    }

    public void filter(Class<?> type, String[] fields) {
        addToMap(filterMap, type, fields);
    }

    private void addToMap(Map<Class<?>, Set<String>> map, Class<?> type, String[] fields) {
        Set<String> filedSet=new HashSet<>();
        if(fields!=null && fields.length>0){
            filedSet.addAll(Arrays.asList(fields));
        }
        map.put(type, filedSet);
    }

    @Override
    public BeanPropertyFilter findFilter(Object filterId) {
        throw new UnsupportedOperationException("Access to deprecated filters not supported");
    }

    @Override
    public PropertyFilter findPropertyFilter(Object filterId, Object valueToFilter) {

        return new SimpleBeanPropertyFilter() {

            @Override
            public void serializeAsField(Object pojo, JsonGenerator jgen, SerializerProvider prov, PropertyWriter writer)
                    throws Exception {
                if (apply(pojo.getClass(), writer.getName())) {
                    writer.serializeAsField(pojo, jgen, prov);
                } else if (!jgen.canOmitFields()) {
                    writer.serializeAsOmittedField(pojo, jgen, prov);
                }
            }
        };
    }

    public boolean apply(Class<?> type, String name) {
        Set<String> includeFields = includeMap.get(type);
        Set<String> filterFields = filterMap.get(type);
        if (includeFields != null && includeFields.contains(name)) {
            return true;
        }
        if (filterFields != null && !filterFields.contains(name)) {
            return true;
        }
        if (includeFields == null && filterFields == null) {
            return true;
        }

        return false;
    }
}

        注:直接复制即可,过滤时,会调用findPropertyFilter和apply自定义过滤逻辑。

2.2.3 调用动态过滤


package com.test;

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

public class test3 {
    public static void main(String[] args) {
        TbFile tbFile = new TbFile();
        tbFile.setId("条目1");
        tbFile.setCode("A001");
        tbFile.setName("条目");
        tbFile.setStatus("待上架");

        TbContainer tbContainer = new TbContainer();
        tbContainer.setId("盒2");
        tbContainer.setType("Box");
        tbContainer.setTitle("盒");
        tbContainer.setCode("02");

        TbRoom tbRoom = new TbRoom();
        tbRoom.setId("房间1");
        tbRoom.setCode("01");
        tbRoom.setName("房间");
        tbRoom.setDescription("房间描述");

        TbFloor tbFloor = new TbFloor();
        tbFloor.setId("楼层1");
        tbFloor.setName("楼层");
        tbFloor.setDescription("楼层描述");


        tbRoom.setFloor(tbFloor);
        tbContainer.setRoom(tbRoom);
        tbFile.setContainer(tbContainer);

        Map<Class<?>, String[]> includeMap = new HashMap<>();
        includeMap.put(TbFile.class, new String[]{"name", "container"});
        includeMap.put(TbRoom.class, new String[]{"name", "id", "floor"});
        includeMap.put(TbFloor.class, new String[]{"name"});

        Map<Class<?>, String[]> excludeMap = new HashMap<>();
        excludeMap.put(TbContainer.class, new String[]{"type","title"});

        String ss = JsonUtil.toJSONString(tbFile, includeMap, excludeMap);
        System.out.println(ss);
    }
}

        注:file类下有container对象,container对象下有room对象,room对象下有floor对象。

        调用逻辑为file只保留name和container属性;container排除掉type和title属性;room只保留name、id和floor属性;floor只保留name属性。

2.2.4 输出结果:

{
  "name" : "条目",
  "container" : {
    "id" : "盒2",
    "code" : "02",
    "room" : {
      "id" : "房间1",
      "name" : "房间",
      "floor" : {
        "name" : "楼层"
      }
    }
  }
}

        输出结果和上文的调用逻辑中想达到的效果一致。

        至此,用jackson完美实现了对fastjson的快速替换,且实现了序列化时对多层嵌套对象的属性过滤。

开发板推荐:天空星STM32F407VET6开发板

超高性价比 STM32主控 | 超高主频 | 一板兼容百芯 | 比赛神器 | 沉金彩色丝印

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值