【工作中问题解决实践 七】SpringBoot集成Jackson进行对象序列化和反序列化

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 【工作中问题解决实践 七】SpringBoot集成Jackson进行对象序列化和反序列化

去年10月份以来由于公司和家里的事情太多,所以一直没有学习,最近缓过来了,学习的脚步不能停滞啊。回归正题,其实前年在学习springMvc的时候也学习过Jackson【Spring MVC学习笔记 五】SpringMVC框架整合Jackson工具,但是呢只是局限于基本用法,当时也刚进入新项目工作没多久,体会也没有那么深刻。现如今工作中深度用到了Jackson,但是对于Jackson的详细情况心里却没有十分的底,大多数时候都是用到的时候从网上找相关的方法实现copy一份,没有全局的认识,所以这篇博客详细的学习和实践一下Jackson。当然市面上的序列化框架有很多,例如谷歌的Gson,阿里的FastJson,但是因为综合考虑性能(Jackson比较强)、稳定性(Jackson和Gson都比较强),再加上SpringBoot默认集成的就是Jackson,所以对于Jackson掌握清楚后就足以应对大多数工作场景了。

回顾Json格式规则

从结构上看,所有的Json格式数据最终都可以分成三种类型:

  • 第一种类型是scalar(标量),也就是一个单独的string(字符串)或数字(numbers),比如"北京"这个单独的词
  • 第二种类型是sequence(序列),也就是若干个相关的数据按照一定顺序并列在一起,又叫做array(数组)或List(列表),比如["北京","天津"]
  • 第三种类型是mapping(映射),也就是一个名/值对(Name/value),即数据有一个名称,还有一个与之相对应的值,这又称作hash(散列)或dictionary(字典),比如{"城市名称":"北京"}

Json格式规则有如下几种:

  1. 并列的数据之间用逗号分隔
  2. 映射用冒号表示
  3. 并列数据的集合(数组)用方括号[]表示
  4. 映射的集合(对象)用大括号{}表示

大多时候我们会用到对象和Json的序列化与反序列化操作。

SpringBoot集成Jackson

当然第一步就是在Maven中进行Jackson包的引入了,还是从Maven的中央仓库引入最新版本的Jackson:Jackson的Maven仓库地址,我们就使用截止目前Jackson更新的最新版本:

pom文件如下:

<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.14.2</version>
</dependency>

Jackson核心包概览

Jackson包含三部分的核心包:jackson-core、jackson-annotations、jackson-databind

  • jackson-core,核心包,提供基于流模式解析的相关 API,它包括 JsonPaserJsonGenerator。 Jackson 内部实现正是通过高性能的流模式 API 的 JsonGenerator 和 JsonParser 来生成和解析 json。
  • jackson-annotations,注解包,提供标准注解功能;
  • jackson-databind ,数据绑定包, 提供基于对象绑定解析的相关 API ( ObjectMapper ) 和树模型 解析的相关 API (JsonNode);基于"对象绑定" 解析的 API 和"树模型"解析的 API 依赖基于"流模式"解析的 API。
    当然因为jackson-databind 依赖 jackson-corejackson-annotations,所以当添加 jackson-databind 之后, jackson-core 和 jackson-annotations 也随之添加到 Java 项目工程中。

由于我们大多数场景都是处理对象和Json之间的映射关系,所以我们把重点放到对象绑定上来。

核心对象ObjectMapper

大多数的ObjectMapper对象都会被配置到Feature对象。

这篇文章ObjectMapper的一些配置提到的一些默认配置在最新的jackson版本里已经更新了,例如序列化时默认的时间戳格式被取消,序列化时默认的空对象异常也不会抛出了

Jackson基本用法实践

关于Jackson的基本用法如下,直接上代码清单

目标Person对象

我们用来测试的对象

package com.example.springboot.jackson;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDate;
import java.util.List;
/**
 * The type Person.
 *
 * @author tianmaolin004
 * @date 2023 /3/18
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Person {
    private String name;
    private Integer age;
    private List<String> interests;
    private LocalDate birthday;
}

JsonUtils类

包括ObjectMapper对象的初始化以及相关的一些配置,还有转换方法

package com.example.springboot.jackson;
import com.alibaba.druid.util.StringUtils;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
/**
 * The type Json operator.
 *
 * @author tianmaolin004
 * @date 2023 /3/18
 */
@Slf4j
public class JsonUtils {
    private static final ObjectMapper objectMapper = new ObjectMapper();
    private static final String LOCAL_DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss";
    static {
        // 1 序列化及反序列化的时间配置
        JavaTimeModule timeModule = new JavaTimeModule();
        timeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ISO_LOCAL_DATE));
        timeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ISO_LOCAL_DATE));
        timeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ISO_LOCAL_TIME));
        timeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ISO_LOCAL_TIME));
        timeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(LOCAL_DATE_TIME_PATTERN)));
        timeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(LOCAL_DATE_TIME_PATTERN)));
        objectMapper.registerModule(timeModule);
        objectMapper.setDateFormat(new SimpleDateFormat(LOCAL_DATE_TIME_PATTERN));
        //2 忽略反序列化时,对象不存在对应属性的错误,如果不存在该属性,则设置值为null
        objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
        //3 忽略序列化时值为Null元素,不存在该元素,则字符串中无该元素,而不是展示为null
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
    }
    /**
     * 对象转字符串
     *
     * @param <T> the type parameter
     * @param obj the obj
     * @return the string
     */
    public static <T> String obj2Str(T obj) {
        if (obj == null) {
            return null;
        }
        try {
            return obj instanceof String ? (String) obj : objectMapper.writeValueAsString(obj);
        } catch (Exception e) {
            log.error("obj2Str fail");
            return null;
        }
    }
    /**
     * 字符串转对象
     *
     * @param <T>   the type parameter
     * @param str   the str
     * @param clazz the clazz
     * @return the t
     */
    public static <T> T str2Obj(String str, Class<T> clazz) {
        if (StringUtils.isEmpty(str) || clazz == null) {
            return null;
        }
        try {
            return clazz.equals(String.class) ? (T) str : objectMapper.readValue(str, clazz);
        } catch (Exception e) {
            log.error("str2Obj fail");
            return null;
        }
    }
    /**
     * 字符串转对象:泛型模式,一般用于集合
     *
     * @param <T>           the type parameter
     * @param str           the str
     * @param typeReference the type reference
     * @return the t
     */
    public static <T> T str2Obj(String str, TypeReference<T> typeReference) {
        if (StringUtils.isEmpty(str) || typeReference == null) {
            return null;
        }
        try {
            return (T) (typeReference.getType().equals(String.class) ? str : objectMapper.readValue(str, typeReference));
        } catch (Exception e) {
            log.error("str2Obj fail");
            return null;
        }
    }
    /**
     * 字符串转JsonNode
     *
     * @param str the str
     * @return the json node
     */
    public static JsonNode str2JsonNode(String str) {
        if (StringUtils.isEmpty(str)) {
            return null;
        }
        try {
            return objectMapper.readTree(str);
        } catch (Exception e) {
            log.error("str2Obj fail");
            return null;
        }
    }
    /**
     * 对象互转
     *
     * @param <T>         the type parameter
     * @param fromValue   the from value
     * @param toValueType the to value type
     * @return the t
     */
    public static <T> T convertValue(@NonNull Object fromValue, @NonNull Class<T> toValueType) {
        try {
            return objectMapper.convertValue(fromValue, toValueType);
        } catch (Exception e) {
            log.error("str2Obj fail");
            return null;
        }
    }
    /**
     * 对象互转泛型模式
     *
     * @param <T>            the type parameter
     * @param fromValue      the from value
     * @param toValueTypeRef the to value type ref
     * @return the t
     */
    public static <T> T convertValue(@NonNull Object fromValue, @NonNull TypeReference<T> toValueTypeRef) {
        try {
            return objectMapper.convertValue(fromValue, toValueTypeRef);
        } catch (Exception e) {
            log.error("str2Obj fail");
            return null;
        }
    }
}

测试Json转换

测试类及测试结果:

package com.example.springboot.jackson;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import java.time.LocalDate;
import java.util.*;
/**
 * @author tianmaolin004
 * @date 2023/3/18
 */
public class JsonTest {
    public static void main(String[] args) {
        // 序列化操作
        List<String> interests = new ArrayList<>();
        interests.add("跑步");
        interests.add("游泳");
        Map<String, Integer> scores = new HashMap<>(16);
        scores.put("语文", 60);
        scores.put("数学", 80);
        Set<String> loveMovies = new HashSet<>();
        loveMovies.add("士兵突击");
        loveMovies.add("士兵突击");
        loveMovies.add("爱乐之城");
        Person person = Person.builder()
                .name("tml")
                .age(24)
                .interests(interests)
                .scores(scores)
                .loveMovies(loveMovies)
                .birthday(LocalDate.now())
                .build();
        System.out.println("obj2Str,序列化对象");
        System.out.println(JsonUtils.obj2Str(person));
        Person personLoseAttr = Person.builder()
                .name("tml")
                .interests(interests)
                .birthday(LocalDate.now())
                .build();
        System.out.println("obj2Str,序列化缺失元素对象");
        System.out.println(JsonUtils.obj2Str(personLoseAttr));
        Person emptyPerson = new Person();
        System.out.println("obj2Str,序列化空对象");
        System.out.println(JsonUtils.obj2Str(emptyPerson));
        // 反序列化操作
        String personStr = "{\"name\":\"wc\",\"age\":99,\"interests\":[\"跑步\",\"游泳\",\"唱跳\",\"rapper\",\"打游戏\"],\"scores\":{\"英语\":10,\"化学\":100},\"loveMovies\":[\"狂飙\",\"狂飙\"],\"birthday\":\"2023-03-05\"}";
        System.out.println("str2Obj,反序列化对象");
        System.out.println(JsonUtils.str2Obj(personStr, Person.class));
        System.out.println("str2Obj,反序列化对象,由于字符串不符合Json的mapping格式,所以需要特殊判断");
        System.out.println(JsonUtils.str2Obj("北京", String.class));
        System.out.println("str2Obj,反序列化对象,由于集合不能指定元素类型,所以使用泛型方式");
        System.out.println(JsonUtils.str2Obj("[\"跑步\",\"游泳\",\"唱跳\",\"rapper\",\"打游戏\"]", new TypeReference<List<String>>() {
        }));
        String personLoseAttrStr = "{\"age\":99,\"interests\":[\"跑步\",\"游泳\",\"唱跳\",\"rapper\",\"打游戏\"]}";
        System.out.println("str2Obj,反序列化缺失元素的对象");
        System.out.println(JsonUtils.str2Obj(personLoseAttrStr, Person.class));
        String personBadAttrStr = "{\"ag\":99,\"interests\":[\"跑步\",\"游泳\",\"唱跳\",\"rapper\",\"打游戏\"]}";
        System.out.println("str2Obj,反序列化包含不存在元素的对象");
        System.out.println(JsonUtils.str2Obj(personBadAttrStr, Person.class));
        // 反序列化为jsonNode对象,获取属性值并将属性值转为目标类型对象:场景,目标JSON结构复杂,不想映射创建一个对象,只想使用其中部分数据,则先将JSON转为JsonNode对象,获取其部分属性值再转为我们需要的确定性对象
        System.out.println("str2JsonNode,反序列化JsonNode对象");
        JsonNode jsonNode = JsonUtils.str2JsonNode(personStr);
        System.out.println(jsonNode);
        System.out.println(jsonNode.findValues("age"));
        System.out.println(JsonUtils.convertValue(jsonNode.get("interests"), new TypeReference<List<String>>() {
        }));
        System.out.println(JsonUtils.convertValue(jsonNode.get("name"), String.class));
    }
}

打印结果如下:

应用场景:深拷贝

对于List中存在对象的情况下除了循环遍历,对每个元素重建外,通过序列化的方式也能轻松的实现集合的深拷贝

List<FeeRateDiscount> resultPlatDiscount = BeanCopyUtils.deepCopy(platDiscountFeeRates, new TypeReference<List<FeeRateDiscount>>() {
            });
public static <T> T deepCopy(Object src, TypeReference<T> dstClazz) {
        if (null == src) {
            return null;
        }
        return JsonUtils.str2Obj(JsonUtils.obj2Str(src), dstClazz);
    }

总结一下

在不知所以然的时候很容易用错一些最基础的知识,越是基础的知识其发生错误的情况所导致的影响范围也越大,只有踏踏实实的自己尝试过,才知道框架怎么用最好,所以对于新知识最好不要抱有模糊的侥幸心理,这样其实自己心里也不踏实,不敢实践。Jackson是个好的开始,接下来则是Jooq以及Gradle。

相关文章
|
1月前
|
存储 缓存 NoSQL
深入理解Django与Redis的集成实践
深入理解Django与Redis的集成实践
54 0
|
3天前
|
机器学习/深度学习 人工智能 jenkins
软件测试中的自动化与持续集成实践
在快速迭代的软件开发过程中,自动化测试和持续集成(CI)是确保代码质量和加速产品上市的关键。本文探讨了自动化测试的重要性、常见的自动化测试工具以及如何将自动化测试整合到持续集成流程中,以提高软件测试的效率和可靠性。通过案例分析,展示了自动化测试和持续集成在实际项目中的应用效果,并提供了实施建议。
|
15天前
|
jenkins Devops Java
DevOps实践:Jenkins在持续集成与持续部署中的价值
【10月更文挑战第27天】在快速发展的软件开发领域,DevOps实践日益重要。Jenkins作为一款流行的开源自动化服务器,在持续集成(CI)和持续部署(CD)中扮演关键角色。本文通过案例分析,探讨Jenkins在Java项目中的应用,展示其自动化构建、测试和部署的能力,提高开发效率和软件质量。
39 2
|
7天前
|
存储 监控 Devops
DevOps实践:持续集成/持续部署(CI/CD)的实战指南
DevOps实践:持续集成/持续部署(CI/CD)的实战指南
|
10天前
|
JSON 数据格式 索引
Python中序列化/反序列化JSON格式的数据
【11月更文挑战第4天】本文介绍了 Python 中使用 `json` 模块进行序列化和反序列化的操作。序列化是指将 Python 对象(如字典、列表)转换为 JSON 字符串,主要使用 `json.dumps` 方法。示例包括基本的字典和列表序列化,以及自定义类的序列化。反序列化则是将 JSON 字符串转换回 Python 对象,使用 `json.loads` 方法。文中还提供了具体的代码示例,展示了如何处理不同类型的 Python 对象。
|
16天前
|
jenkins Devops 测试技术
DevOps实践:Jenkins在持续集成与持续部署中的价值
【10月更文挑战第26天】随着DevOps理念的普及,Jenkins作为一款开源自动化服务器,在持续集成(CI)与持续部署(CD)中发挥重要作用。本文通过某中型互联网企业的实际案例,展示了Jenkins如何通过自动化构建、持续集成和持续部署,显著提升开发效率、代码质量和软件交付速度,帮助企业解决传统手工操作带来的低效和错误问题。
44 4
|
20天前
|
存储 安全 Java
Java编程中的对象序列化与反序列化
【10月更文挑战第22天】在Java的世界里,对象序列化和反序列化是数据持久化和网络传输的关键技术。本文将带你了解如何在Java中实现对象的序列化与反序列化,并探讨其背后的原理。通过实际代码示例,我们将一步步展示如何将复杂数据结构转换为字节流,以及如何将这些字节流还原为Java对象。文章还将讨论在使用序列化时应注意的安全性问题,以确保你的应用程序既高效又安全。
|
1月前
|
运维 监控 Devops
DevOps实践:持续集成与部署的自动化之旅
【10月更文挑战第7天】在软件开发领域,DevOps已成为提升效率、加速交付和确保质量的关键策略。本文将深入探讨如何通过实施持续集成(CI)和持续部署(CD)来自动化开发流程,从而优化运维工作。我们将从基础概念入手,逐步过渡到实际操作,包括工具选择、流程设计以及监控和反馈机制的建立。最终,我们不仅会展示如何实现这一自动化流程,还会讨论如何克服常见的挑战,以确保成功实施。
63 9
|
10天前
|
运维 Devops jenkins
DevOps实践之持续集成与持续交付
【10月更文挑战第32天】在软件开发的快节奏世界中,DevOps已经成为提升效率和质量的关键策略。通过将开发(Development)和运维(Operations)紧密结合,DevOps促进了更快速的软件发布和更高的可靠性。本文将深入探讨DevOps的核心组成部分——持续集成(CI)和持续交付(CD),并展示如何通过实际代码示例实现它们,以帮助团队构建更加高效和稳定的软件发布流程。
|
1月前
|
存储 Java
Java编程中的对象序列化与反序列化
【10月更文挑战第9天】在Java的世界里,对象序列化是连接数据持久化与网络通信的桥梁。本文将深入探讨Java对象序列化的机制、实践方法及反序列化过程,通过代码示例揭示其背后的原理。从基础概念到高级应用,我们将一步步揭开序列化技术的神秘面纱,让读者能够掌握这一强大工具,以应对数据存储和传输的挑战。

热门文章

最新文章