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。
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);
}
默认情况下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);
如果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)
默认情况下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);
当反序列化的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);
当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)
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
默认情况下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类型,浮点数也一样。
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=曹操)
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=曹操)
默认情况下,标准的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=曹操)
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"));
最后是自己的一点感慨:又荒废一个多月没更新文章,坚持了几个月的博客习惯,又得重新开始。人的惰性太可怕了,在不知不觉中就让人养成了不好的习惯,最可怕的是自己还觉察、感知不到。今天看了一份统计局的报告,包含内容主要有三点:
看到这份报告联想到今年比较火的经济学的一本书《贫穷的本质》里的观点,基本是一致的,陷入贫穷陷阱的原因是那么的相似。
2020年11月22日 晚 于北京记