1.Jackson基础信息

Jackson是当前用得比较广泛的序列化和反序列化 json 的 Java 开源框架。Jackson社区相对比较活跃,更新速度也比较快,从Github中的统计来看,Jackson 是最流行的 json 解析器之一 。 Spring MVC和Spring Boot的默认 json解析器便是 Jackson。Jackson 的核心模块由三部分组成:

  • jackson-core: 核心包,提供基于"流模式"解析的相关 API,它包括 JsonPaser 和 JsonGenerator。 Jackson内部实现正是通过高性能的流模式API的JsonGenerator 和 JsonParser 来生成和解析 json。

  • jackson-annotations: 注解包,提供标准注解功能。

  • jackson-databind: 数据绑定包, 提供基于"对象绑定" 解析的相关 API ( ObjectMapper ) 和"树模型" 解析的相关 API (JsonNode);基于"对象绑定" 解析的 API 和"树模型"解析的 API 依赖基于"流模式"解析的 API。

maven依赖

<dependency>
	<groupId>com.fasterxml.jackson.core</groupId>
	<artifactId>jackson-databind</artifactId>
	<version>2.9.1</version>
</dependency>

jackson-databind 依赖 jackson-core 和 jackson-annotations,当添加 jackson-databind 之后, jackson-core 和 jackson-annotations 也随之添加到 Java 项目工程中。在添加相关依赖包之后,就可以使用 Jackson。

2.ObjectMapper配置信息

Jackson提供了ObjectMapper来供程序员“定制化控制”序列化、反序列化的过程。objectMapper在调用writeValue()序列化 或 调用readValue()反序列化方法之前,往往需要设置 ObjectMapper 的相关配置信息,这些配置信息作用在 java 对象的所有属性上,表示在进行序列化和反序列化时进行一些特殊的处理。ObjectMapper的相关的配置属性主要在Feature这个枚举类里,Feature的源码和其作用如下:

public enum Feature {
	// Low-level I/O handling features:支持低级I/O操作特性
	/**
	 * 自动关闭源:默认true_启用(即:解析json字符串后,自动关闭输入流)
	 * 该特性,决定了解析器是否可以自动关闭非自身的底层输入源
	 * 1.禁用:应用程序将分开关闭底层的{@link InputStream} and {@link Reader}
	 * 2.启用:解析器将关闭上述对象,其自身也关闭,此时input终止且调用{@link JsonParser#close}
	 */
	AUTO_CLOSE_SOURCE(true),

	/**
	 * Support for non-standard data format constructs:支持非标准数据格式的json
	 * 该特性,决定了解析器是否可以解析含有Java/C++注释样式的JSON串(如:/*或//的注释符)
	 * 默认false:不解析含有注释符(即:true时能解析含有注释符的json串)
	 * 注意:该属性默认是false,因此必须显式允许,即通过JsonParser.Feature.ALLOW_COMMENTS 配置为true。
	 */
	ALLOW_COMMENTS(false),

	/**
	 * 默认false:不解析含有另外注释符
	 * 该特性,决定了解析器是否可以解析含有以"#"开头并直到一行结束的注释样式(这样的注释风格通常也用在脚本语言中)
	 * 注意:标准的json字符串格式没有含有注释符(非标准),然而则经常使用<br>
	 */
	ALLOW_YAML_COMMENTS(false),

	/**
	 * 这个特性决定parser是否能解析属性名字没有加双引号的json串(这种形式在Javascript中被允许,但是JSON标准说明书中没有)。
	 *(默认情况下,标准的json串里属性名字都需要用双引号引起来。比如:{age:12, name:"曹操"}非标准的json串,默认不能解析)
	 * 注意:由于JSON标准上需要为属性名称使用双引号,所以这也是一个非标准特性,默认是false的。
	 * 同样,需要设置JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES为true,打开该特性。
	 *
	 */
	ALLOW_UNQUOTED_FIELD_NAMES(false),

	/**
	 * 默认false:不解析含有单引号的字符串或字符
	 * 该特性,决定了解析器是否可以解析单引号的字符串或字符(如:单引号的字符串,单引号'\'')
	 * 注意:可作为其他可接受的标记,但不是JSON的规范
	 */
	ALLOW_SINGLE_QUOTES(false),

	/**
	 * 允许:默认false不解析含有结束语控制字符
	 * 该特性,决定了解析器是否可以解析结束语控制字符(如:ASCII<32,包含制表符\t、换行符\n和回车\r)
	 * 注意:设置false(默认)时,若解析则抛出异常;若true时,则用引号即可转义
	 */
	ALLOW_UNQUOTED_CONTROL_CHARS(false),

	/**
	 * 可解析反斜杠引用的所有字符,默认:false,不可解析
	 */
	ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER(false),

	/**
	 * 可解析以"0"为开头的数字(如: 000001),解析时则忽略0,默认:false,不可解析,若有则抛出异常
	 */
	ALLOW_NUMERIC_LEADING_ZEROS(false),

	/**
	 * 可解析非数值的数值格式(如:正无穷大,负无穷大,Integer或浮点数类型属性赋值NaN的JSON串)
	 * 该特性允许parser可以识别"Not-a-Number" (NaN)标识集合作为一个合法的浮点数。
	 * 默认:false,不能解析
	 */
	 ALLOW_NON_NUMERIC_NUMBERS(false),

	 /**
	  * 默认:false,不检测JSON对象重复的字段名,即:相同字段名都要解析
	  * true时,检测是否有重复字段名,若有,则抛出异常{@link JsonParseException}
	  * 注意:检查时,解析性能下降,时间超过一般情况的20-30%
	  */
	 STRICT_DUPLICATE_DETECTION(false),

	 /**
	  * 默认:false,底层的数据流(二进制数据持久化,如:图片,视频等)全部被output,若读取一个位置的字段,则抛出异常
	  * true时,则忽略未定义
	  */
	 IGNORE_UNDEFINED(false),

	 /**
	  * 默认:false,JSON数组中不解析漏掉的值,若有,则会抛出异常{@link JsonToken#VALUE_NULL}
	  * true时,可解析["value1",,"value3",]最终为["value1", null, "value3", null]空值作为null
	  */
	 ALLOW_MISSING_VALUES(false);
}

(1)忽略未知字段

默认情况下Jackson要求JSON字符串消息 和 Java类中的字段必须一一相对应,否则反序列解析JSON字符串时会报错。当然也可以通过配置Jackson的ObjectMapper属性使Jackson在反序列化时,忽略在 json字符串中存在但 Java 对象不存在的属性。

#例如:
#1)java对象属性
@Data
public class User implements Serializable {
    private Integer age;
    private String name;
}

#2)需要反序列化JSON字符串
public static void main(String[] args) throws Exception {
	ObjectMapper objectMapper = new ObjectMapper();
	String json = "{\"age\":10,\"name\":\"曹操\",\"class\":\"语文\"}";
	#会报错:因为json字符串的属性和java对象属性没有一一对应
	User user = objectMapper.readValue(json, User.class);
	System.out.println(user);
}

#异常:
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "class"

#3)解决办法:忽略未知字段 DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES默认是true。
	objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

(2)属性为NULL不被序列化

如果java对象的属性为NULL则不参与序列化,即java对象序列化后的json串里不出现属性为null的字段。该功能可以使用@JsonInclude注解,也可以设置objectMapper属性。

#示例:
#1)java对象属性
@Data
public class User implements Serializable {
    private Integer age;
    private String name;
}

#2)序列化属性有为null的对象
public static void main(String[] args) throws Exception {
	ObjectMapper objectMapper = new ObjectMapper();
	User user = new User();
	user.setAge(10);
	String string = objectMapper.writeValueAsString(user);
	System.out.println(string);
}
#输出:json字段带null
{"age":10,"name":null}

#3)解决办法:关闭属性为NULL还序列化功能,默认是开启。
@JsonInclude(JsonInclude.Include.NON_NULL) 注解可以加到类或属性上,加到类上表示多所有属性都有效。objectMapper.setSerializationInclusion(Include.NON_NULL)

(3)对象属性为空时,默认序列化会失败

默认情况下ObjectMapper序列化没有属性的空对象时会抛异常。可以通过SerializationFeature.FAIL_ON_EMPTY_BEANS设置当对象没有属性时,让其序列化能成功,不抛异常。

#示例:
#1)java对象属性:没有任何属性
@Data
public class User implements Serializable {
}

#2)默认序列化失败,会抛异常
public static void main(String[] args) throws Exception {
	ObjectMapper objectMapper = new ObjectMapper();
	#默认是true,空对象不让序列化
	//objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, true);
	User user = new User();
	String string = objectMapper.writeValueAsString(user); #会抛异常
	System.out.println(string);
}
#抛异常:com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class com.igetcool.common.model.User and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)

#3)解决办法:关闭空对象不让序列化功能
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);

(4)json字符串值带反斜杠("\"),默认反序列化会失败

当反序列化的JSON串里带有反斜杠时,默认objectMapper反序列化会失败,抛出异常Unrecognized character escape。可以通过Feature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER来设置当反斜杠存在时,能被objectMapper反序列化。

#示例:
#1)java对象属性
@Data
public class User implements Serializable {
    private Integer age;
    private String name;
}

#2)反序列化字符串带反斜杠,会抛异常。
public static void main(String[] args) throws Exception {
	ObjectMapper objectMapper = new ObjectMapper();
	#objectMapper默认是false
	objectMapper.configure(Feature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER, false);
	#name的值带反斜杠,默认情况下objectMapper解析器会反序列化失败
	String json = "{\"age\":10,\"name\":\"曹\\操\"}";
	User user = objectMapper.readValue(json, User.class);
	System.out.println(user);
}
#输出:com.fasterxml.jackson.databind.JsonMappingException: Unrecognized character escape '操' (code 25805 / 0x64cd)

#3)解决办法:设置解析能识别JSON串里的注释符
objectMapper.configure(Feature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER, true);

(5)json字符串带注释符,默认反序列化会失败

当json字符串里带注释符时,默认情况下解析器不能解析。Feature.ALLOW_COMMENTS特性决定解析器是否允许解析使用Java/C++ 样式的注释(包括'/'+'*' 和'//' 变量)。

#1)例如:如下反序列化JSON串里带注释,默认情况下objectMapper不能反序列化解析成对象,需要设置ALLOW_COMMENTS。
{
	"age": 10
	//,
	//"name": "曹操"
}

#2)默认情况下,上面这种带注释的JSON串,objectMapper解析器是不能解析的。
public static void main(String[] args) throws Exception {
	ObjectMapper objectMapper = new ObjectMapper();
	#默认是false,不能解析带注释的JSON串
	objectMapper.configure(Feature.ALLOW_COMMENTS, true);
	String json  = "{"
		+"\"age\"" + ":" + 10 +
		"/*" + "," +
		"\"name\"" + ":" + "\"曹操\"*/" + 
	"}";
	//Feature.ALLOW_COMMENTS打开时,JSON里的注释符会被过滤掉,解析器能解析
	User user = objectMapper.readValue(json, User.class);
	System.out.println(user);
}

#输出:User(age=10, name=null)

(6)Json字符串里数字类型值为正无穷、负无穷或NaN时,默认反序列化会失败

objectMapper解析器默认不能识别识别 "Not-a-Number" (NaN)标识集合作为一个合法的浮点数 或 一个int数,objectMapper默认该功能是关闭的。

#1)例如:反序列化的JSON串里包含了数字类型的属性值为NaN,默认objectMapper解析器是不能解析的。
{
	"age": NaN,
	"name": "曹操"
}
#java对象属性:age是数字类型
@Data
public class User implements Serializable {
    private Integer age;
    private String name;
}

#2)数字类型 或 浮点类型值为NaN时,默认objectMapper解析器是不能解析的,需要开启Feature.ALLOW_NON_NUMERIC_NUMBERS。
public static void main(String[] args) throws Exception {
	ObjectMapper objectMapper = new ObjectMapper();
	objectMapper.configure(Feature.ALLOW_NON_NUMERIC_NUMBERS, true);
	String json = "{\"age\":NaN, \"name\":\"曹操\"}";
	User user = objectMapper.readValue(json, User.class);
	System.out.println(user);
	//输出是无穷大
	System.out.println(0.0/0.0);
}
#输出:
User(age=NaN, name=曹操)
NaN

#注意:浮点类型 或 数字类型都可以接受NaN值,反序列化需要开启Feature.ALLOW_NON_NUMERIC_NUMBERS

(7)反序列化可解析以"0"为开头的数字

默认情况下objectMapper解析器是不能解析以"0"为开头的数字,需要开启Feature.ALLOW_NUMERIC_LEADING_ZEROS才能使用。

#1)例如java对象属性age是int类型
@Data
public class User implements Serializable {
    private Integer age;
    private String name;
}

#2)JSON字符串的age值是"0"为开头的数字,objectMapper默认是不能解析的
public static void main(String[] args) throws Exception {
	ObjectMapper objectMapper = new ObjectMapper();
	objectMapper.configure(Feature.ALLOW_NUMERIC_LEADING_ZEROS, true);
	//“0”开头的数字
	String json = "{\"age\":0012, \"name\":\"曹操\"}";
	User user = objectMapper.readValue(json, User.class);
	System.out.println(user);
}
#输出:User(age=12, name=曹操)

#注意:除了int类型,浮点数也一样。

(8)Json反序列化可以解析单引号包住的属性名称和字符串值

parser解析器默认情况下不能识别单引号包住的属性和属性值,默认下该属性也是关闭的。需要设置JsonParser.Feature.ALLOW_SINGLE_QUOTES为true。

#1)objectMapper默认情况下是不能解析带单引号的json串的,需要开启JsonParser.Feature.ALLOW_SINGLE_QUOTES属性
public static void main(String[] args) throws Exception {
	ObjectMapper objectMapper = new ObjectMapper();
	#需要开启单引号解析属性,默认是false
	objectMapper.configure(Feature.ALLOW_SINGLE_QUOTES, true);
	String json = "{'age':12, 'name':'曹操'}";
	User user = objectMapper.readValue(json, User.class);
	System.out.println(user);
}
#输出:如果Feature.ALLOW_SINGLE_QUOTES设置为false时,解析器会解析失败
User(age=12, name=曹操)

(9)反序列Json字符串中包含制控制字符

Feature.ALLOW_UNQUOTED_CONTROL_CHARS该特性决定parser是否允许JSON字符串包含非引号控制字符(值小于32的ASCII字符,包含制表符\t、换行符\n和回车\r)。 如果该属性关闭,则如果遇到这些字符,则会抛出异常。

#默认情况下parser解析器是不能解析包含控制字符的json字符串,需要设置ALLOW_UNQUOTED_CONTROL_CHARS属性
#1)处理问题:JSON串里属性或属性值包含控制字符,解析器能解析。
{
	"age": 12,
	"name": "曹操\n"
}

#2)示列
public static void main(String[] args) throws Exception {
	ObjectMapper objectMapper = new ObjectMapper();
	//开启单引号解析
	objectMapper.configure(Feature.ALLOW_SINGLE_QUOTES, true);
	//开启JSON字符串包含非引号控制字符的解析(\n换行符)
	objectMapper.configure(Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true);
	String json = "{'age':12, 'name':'曹操\n'}";
	User user = objectMapper.readValue(json, User.class);
	System.out.println(user);
}
#输出:输出有换行效果
User(age=12, name=曹操)

(10)反序列Json字符串中属性名没有双引号

默认情况下,标准的json串里属性名字都需要用双引号引起来。比如:{age:12, name:"曹操"}非标准的json串,解析器默认不能解析,需要设置Feature.ALLOW_UNQUOTED_FIELD_NAMES属性来处理这种没有双引号的json串。

#1)ALLOW_UNQUOTED_FIELD_NAMES处理的问题:属性名没有双引号的非标准json字符串,能被解析器识别。
{
	age: 12,
	name: "曹操"
}
#2)示列
public static void main(String[] args) throws Exception {
	ObjectMapper objectMapper = new ObjectMapper();
	//开启单引号解析
	objectMapper.configure(Feature.ALLOW_SINGLE_QUOTES, true);
	//开启属性名没有双引号的非标准json字符串
	objectMapper.configure(Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);

	String json = "{age:12, name:'曹操'}";
	User user = objectMapper.readValue(json, User.class);
	System.out.println(user);
}
#输出:
User(age=12, name=曹操)

(11)时间格式化

Jackson对时间(Date)序列化的转换格式默认是时间戳,可以取消时间的默认时间戳转化格式;默认时间戳转化格式取消后在序列化时日期格式默认为 yyyy-MM-dd'T'HH:mm:ss.SSSZ,同时需要设置要展现的时间格式。

#(1)Jackson对时间(Date)序列化的转换格式默认是时间戳
public static void main(String[] args) throws JsonProcessingException {
	ObjectMapper mapper = new ObjectMapper();
	//WRITE_DATES_AS_TIMESTAMPS属性值默认就是true
	//mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, true);
	String date = mapper.writeValueAsString(new Date());
	System.out.println("默认是时间戳格式:" + date);
}
//输出:
默认是时间戳格式:1605848842390

#(2)取消时间的默认时间戳转化格式后,再序列化时日期格式默认为 yyyy-MM-dd'T'HH:mm:ss.SSSZ
public static void main(String[] args) throws JsonProcessingException {
	ObjectMapper mapper = new ObjectMapper();
	//WRITE_DATES_AS_TIMESTAMPS属性值默认就是true
	mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
	String date = mapper.writeValueAsString(new Date());
	System.out.println("默认时格式:" + date);
}
//输出:
默认时格式:"2020-11-20T05:12:22.868+0000"

#(3)取消Jackson时间的默认时间戳转化格式,并设置需要展现的时间格式
public static void main(String[] args) throws JsonProcessingException {
    ObjectMapper mapper = new ObjectMapper();
    //取消时间的转化格式默认是时间戳,可以取消,同时需要设置要表现的时间格式
    mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
    mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

    String date = mapper.writeValueAsString(new Date());
    System.out.println("指定时间时格式:" + date);
}
//输出:
指定时间时格式:"2020-11-20 13:14:56"

Springboot使用的默认json解析框架是jackjson框架,在格式化Model时对Date属性指定时间格式方式有以下三种方法:

#(1)配置文件:将spring的jackson日期格式写在配置文件中即可。
spring:
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8
    serialization:
      write-dates-as-timestamps: false

#或者写成以下格式(主要取决于配置文件是什么格式的)

spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8
spring.jackson.serialization.write-dates-as-timestamps=false

#(2)注解:在实体Date类型的字段上使用@JsonFormat注解格式化日期
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT + 8")
private Date createTime;

#(3)设置ObjectMapper属性:通过下面方式取消timestamps形式,并设置时间格式
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

3.最后

最后是自己的一点感慨:又荒废一个多月没更新文章,坚持了几个月的博客习惯,又得重新开始。人的惰性太可怕了,在不知不觉中就让人养成了不好的习惯,最可怕的是自己还觉察、感知不到。今天看了一份统计局的报告,包含内容主要有三点:

  • (1)低收入群体平均每天看电视的时间,将近高收入群体的两倍。
  • (2)高收入群体平均每天阅读的时间,是低收入群体的3.2倍。
  • (3)高收入群体里有21%有很好的阅读习惯,低收入群体里有阅读行为的占6.6%。

看到这份报告联想到今年比较火的经济学的一本书《贫穷的本质》里的观点,基本是一致的,陷入贫穷陷阱的原因是那么的相似。

 

                                                                                                     2020年11月22日 晚  于北京记

 

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐