去年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格式规则有如下几种:
- 并列的数据之间用逗号
,
分隔 - 映射用冒号
:
表示 - 并列数据的集合(数组)用方括号
[]
表示 - 映射的集合(对象)用大括号
{}
表示
大多时候我们会用到对象和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,它包括
JsonPaser
和JsonGenerator
。 Jackson 内部实现正是通过高性能的流模式 API 的 JsonGenerator 和 JsonParser 来生成和解析 json。 - jackson-annotations,注解包,提供标准注解功能;
- jackson-databind ,数据绑定包, 提供基于对象绑定解析的相关 API (
ObjectMapper
) 和树模型 解析的相关 API (JsonNode
);基于"对象绑定" 解析的 API 和"树模型"解析的 API 依赖基于"流模式"解析的 API。
当然因为jackson-databind
依赖jackson-core
和jackson-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。