参考
- 用好Jackson,操作Json节省一半时间
- 最棒的Json解析工具Jackson,看这一篇就够了
- Spring Boot框架中使用Jackson的处理总结
Jackson 是当前用的比较广泛的,用来序列化和反序列化 json 的 Java 的开源框架。
Jackson 社区相对比较活跃,更新速度也比较快, 从 Github 中的统计来看,Jackson 是最流行的 json 解析器之一 。
Spring MVC 的默认 json 解析器便是 Jackson。
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-coreartifactId>
<version>2.9.6version>
dependency>
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-annotationsartifactId>
<version>2.9.6version>
dependency>
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-databindartifactId>
<version>2.9.6version>
dependency>
默认情况下,Jackson通过将JSON字段的名称与Java对象中的getter和setter方法进行匹配,将JSON对象的字段映射到Java对象中的属性。Jackson删除了getter和setter方法名称的“get”和“set”部分,并将其余名称的第一个字符转换为小写。
例如,名为brand的JSON字段与名为getBrand()和setBrand()的Java getter和setter方法匹配。名为engineNumber的JSON字段将与名为getEngineNumber()和setEngineNumber()的getter和setter匹配。
如果需要以其他方式将JSON对象字段与Java对象字段匹配,则需要使用自定义序列化器和反序列化器,或者使用一些Jackson注解。
ObjectMapper的相关的配置属性主要在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字符串格式没有含有注释符(非标准),然而则经常使用
*/
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);
}
//序列化的时候序列对象的那些属性
//JsonInclude.Include.NON_DEFAULT 属性为默认值不序列化
//JsonInclude.Include.ALWAYS 所有属性
//JsonInclude.Include.NON_EMPTY 属性为 空(“”) 或者为 NULL 都不序列化
//JsonInclude.Include.NON_NULL 属性为NULL 不序列化
mapper.setSerializationInclusion(JsonInclude.Include.ALWAYS);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
如果JSON字符串包含其值设置为null的字段(对于在相应的Java对象中是基本数据类型(int,long,float,double等)的字段),Jackson ObjectMapper默认会处理基本数据类型为null的情况
我们可以将Jackson ObjectMapper默认配置为失效,这样基本数据为null就会转换失败。
objectMapper.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, true);
在FAIL_ON_NULL_FOR_PRIMITIVES配置值设置为true的情况下,尝试将空JSON字段解析为基本类型Java字段时会遇到异常。默认情况下,Jackson ObjectMapper会忽略原始字段的空值。
示例
{ "brand":"Toyota", "doors":null }
@Data
public class Car {
private String brand = null;
private int doors = 0;
}
如果在FAIL_ON_NULL_FOR_PRIMITIVES配置值设置为true的情况下,把JSON字符串转成Java对象会抛异常。
Feature.ALLOW_UNQUOTED_CONTROL_CHARS该特性决定parser是否允许JSON字符串包含非引号控制字符(值小于32的ASCII字符,包含制表符\t、换行符\n和回车\r)。 如果该属性关闭,则如果遇到这些字符,则会抛出异常。
//开启单引号解析
objectMapper.configure(Feature.ALLOW_SINGLE_QUOTES, true);
//开启JSON字符串包含非引号控制字符的解析(\n换行符)
objectMapper.configure(Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true)
ObjectMapper objectMapper = new ObjectMapper();
String carJson = "{ \"brand\" : \"Mercedes\", \"doors\" : 5 }";
Car car = objectMapper.readValue(carJson, Car.class);
ObjectMapper objectMapper = new ObjectMapper();
String carJson = "{ \"brand\" : \"Mercedes\", \"doors\" : 4 }";
Reader reader = new StringReader(carJson);
Car car = objectMapper.readValue(reader, Car.class);
ObjectMapper objectMapper = new ObjectMapper();
InputStream input = new FileInputStream("data/car.json");
Car car = objectMapper.readValue(input, Car.class);
ObjectMapper objectMapper = new ObjectMapper();
String carJson = "{ \"brand\" : \"Mercedes\", \"doors\" : 5 }";
byte[] bytes = carJson.getBytes("UTF-8");
Car car = objectMapper.readValue(bytes, Car.class);
ObjectMapper objectMapper = new ObjectMapper();
File file = new File("data/car.json");
Car car = objectMapper.readValue(file, Car.class);
示例使用文件URL,也可以使用HTTP URL
ObjectMapper objectMapper = new ObjectMapper();
URL url = new URL("file:data/car.json");
Car car = objectMapper.readValue(url, Car.class);
读取对象数组还可以与字符串以外的其他JSON源一起使用。例如,文件,URL,InputStream,Reader等。
String jsonArray = "[{\"brand\":\"ford\"}, {\"brand\":\"Fiat\"}]";
ObjectMapper objectMapper = new ObjectMapper();
Car[] cars2 = objectMapper.readValue(jsonArray, Car[].class);
String jsonArray = "[{\"brand\":\"ford\"}, {\"brand\":\"Fiat\"}]";
ObjectMapper objectMapper = new ObjectMapper();
List<Car> cars1 = objectMapper.readValue(jsonArray, new TypeReference<List<Car>>(){});
如果事先不知道将要解析的确切JSON结构,这种方法是很有用的。通常,会将JSON对象读入Java Map。JSON对象中的每个字段都将成为Java Map中的键值对。
String jsonObject = "{\"brand\":\"ford\", \"doors\":5}";
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> jsonMap = objectMapper.readValue(jsonObject,
new TypeReference<Map<String,Object>>(){});
objectMapper.writeValue(new FileOutputStream("data/output-2.json"), car);
String json = objectMapper.writeValueAsString(car);
默认情况下,Jackson会将java.util.Date对象序列化为其long型的值,该值是自1970年1月1日以来的毫秒数
// transaction对象包含Date类型字段
String output = objectMapper.writeValueAsString(transaction);
// ("type":"transfer","date":1590229541305}
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
objectMapper.setDateFormat(dateFormat);
// transaction对象包含Date类型字段
String output2 = objectMapper.writeValueAsString(transaction);
// {"date":"2020-05-23","type":"transfer"}
除了使用*@JsonTypeInfo*注解来实现多态数据绑定,还可以使用全局Default Typing机制,开启DefaultTyping就可以让特定类型对象序列化时都存储类型信息,所以反序列时可以知道多态类型的数据的具体类型。
// default to using DefaultTyping.OBJECT_AND_NON_CONCRETE
objectMapper.enableDefaultTyping();
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
DefaultTyping的选项:
用于属性,把属性的名称序列化时转换为另外一个名称。
@JsonProperty("birth_date")
private Date birth;
用于属性或者方法,把属性的格式序列化时转换成指定的格式。
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone="GMT+8")
private LocalDateTime createTime;
用于类, 指定属性在序列化时 json 中的顺序。通常,Jackson会按照在类中找到的顺序序列化PersonPropertyOrder中的属性。
@JsonPropertyOrder({"birth_Date", "name"})
public class Person{}
当将JSON读入对象时,应将此setter方法的名称与JSON数据中的属性名称匹配。如果Java类内部使用的属性名称与JSON文件中使用的属性名称不同,这个注解就很有用了。
{
"id" : 1234,
"name" : "John"
}
public class Person {
private long personId = 0;
private String name = null;
public long getPersonId() { return this.personId; }
@JsonSetter("id")
public void setPersonId(long personId) { this.personId = personId; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
用于告诉Jackson,应该通过调用getter方法而不是通过直接字段访问来获取某个字段值。如果您的Java类使用jQuery样式的getter和setter名称,则@JsonGetter注解很有用。
例如,您可能拥有方法personId()和personId(long id),而不是getPersonId()和setPersonId()。
public class PersonGetter {
private long personId = 0;
@JsonGetter("id")
public long personId() { return this.personId; }
@JsonSetter("id")
public void personId(long personId) { this.personId = personId; }
}
用于告诉Jackson该Java对象具有一个构造函数,该构造函数可以将JSON对象的字段与Java对象的字段进行匹配。
@JsonCreator注解在无法使用@JsonSetter注解的情况下很有用。例如,不可变对象没有任何设置方法,因此它们需要将其初始值注入到构造函数中。
用于构造方法,和 @JsonProperty 配合使用,适用有参数的构造方法。
{
"id" : 1234,
"name" : "John"
}
public class PersonImmutable {
private long id = 0;
private String name = null;
@JsonCreator
public PersonImmutable(
@JsonProperty("id") long id,
@JsonProperty("name") String name ) {
this.id = id;
this.name = name;
}
public long getId() {
return id;
}
public String getName() {
return name;
}
}
用于属性或者方法,设置未反序列化的属性名和值作为键值存储到 map 中,这仅对无法识别的字段有效。
public class Bag {
private Map<String, Object> properties = new HashMap<>();
@JsonAnySetter
public void set(String fieldName, Object value){
this.properties.put(fieldName, value);
}
public Object get(String fieldName){
return this.properties.get(fieldName);
}
}
{
"id" : 1234,
"name" : "John"
}
Jackson无法直接将此JSON对象的id和name属性映射到Bag类,因为Bag类不包含任何公共字段或setter方法。可以通过添加@JsonAnySetter注解来告诉Jackson为所有无法识别的字段调用set()方法
用于方法 ,获取所有未序列化的属性,可以将Map用作要序列化为JSON的属性的容器
public class PersonAnyGetter {
private Map<String, Object> properties = new HashMap<>();
@JsonAnyGetter
public Map<String, Object> properties() {
return properties;
}
}
properties中的所有键值对都将作为PersonAnyGetter对象的一部分序列化为JSON。
在将JSON读取到Java对象中以及将Java对象写入JSON时,都将忽略该属性。
public class PersonIgnore {
@JsonIgnore
public long personId = 0;
public String name = null;
}
用于指定要忽略的类的属性列表。
@JsonIgnoreProperties({"firstName", "lastName"})
public class PersonIgnoreProperties {
public long personId = 0;
public String firstName = null;
public String lastName = null;
}
用于将整个类型(类)标记为在使用该类型的任何地方都将被忽略。
public class PersonIgnoreType {
@JsonIgnoreType
public static class Address {
public String streetName = null;
public String houseNumber = null;
public String zipCode = null;
public String city = null;
public String country = null;
}
public long personId = 0;
public String name = null;
public Address address = null;
}
在读写对象时包括非public修饰的属性。
JsonAutoDetect.Visibility类包含与Java中的可见性级别匹配的常量:
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY )
public class PersonAutoDetect {
private long personId = 123;
public String name = null;
}
仅在某些情况下包括属性。
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class PersonInclude {
public long personId = 0;
public String name = null;
}
告诉Jackson该属性值应直接写入JSON输出。如果该属性是字符串,Jackson通常会将值括在引号中,但是如果使用@JsonRawValue属性进行注解,Jackson将不会将值括在引号中。
为了更清楚@JsonRawValue的作用,看看没有使用@JsonRawValue的此类:
public class PersonRawValue {
public long personId = 0;
public String address = "$#";
}
Jackson会将其序列化为以下JSON字符串:
{"personId":0,"address":"$#"}
现在,我们将@JsonRawValue添加到address属性,如下所示:
public class PersonRawValue {
public long personId = 0;
@JsonRawValue
public String address = "$#";
}
现在,当对地址属性进行序列化时,jackson将省略引号。因此,序列化的JSON如下所示,它是无效的JSON。
{"personId":0,"address":$#}
如果address属性包含一个JSON字符串,那么该JSON字符串将被序列化为最终的JSON对象,作为JSON对象结构的一部分,而不仅是序列化为JSON对象的address字段中的字符串。
public class PersonRawValue {
public long personId = 0;
@JsonRawValue
public String address =
"{ \"street\" : \"Wall Street\", \"no\":1}";
}
Jackson会将其序列化为以下JSON:
{"personId":0,"address":{ "street" : "Wall Street", "no":1}}
请注意,JSON字符串现在如何成为序列化JSON结构的一部分。
没有@JsonRawValue注解,Jackson会将对象序列化为以下JSON:
{"personId":0,"address":"{ \"street\" : \"Wall Street\", \"no\":1}"}
Jackson将在自定义序列化返回的String内转义任何引号
public class PersonValue {
public long personId = 0;
public String name = null;
@JsonValue
public String toJson(){
return this.personId + "," + this.name;
}
}
要求Jackson序列化PersonValue对象所得到的输出是:
"0,null"
对象序列化不会遇到任何挑战。因为不管变量的真实类型是什么,Jackson 总是会通过所有的 getter
方法来找到所有的属性和值,并序列化到 Json 中。
反序列化成对象时,如果是要反序列化成抽象类的某个子类,因为程序不知道JSON对应是哪个子类,所以会抛异常。
当抽象类或者接口需要序列化和反序列化时,需要用到@JsonTypeInfo
注解开启多态类型处理。这个注解可以用到属性上,也可以用到类上,影响的只是作用范围,对效果没什么影响。
以下是@JsonTypeInfo的属性说明:
use:定义使用哪一种类型识别码,它有下面几个可选值:
include(可选):指定识别码是如何被包含进去的,它有下面几个可选值:
property(可选):制定识别码的属性名称,此属性只有满足以下两个条件才生效
defaultImpl(可选):如果类型识别码不存在或者无效,可以使用该属性来制定反序列化时使用的默认类型
visible(可选,默认为false):propery中的属性是否反序列化到POJO中属性定义了类型标识符的值是否会通过JSON流成为反序列化器的一部分,默认为fale,也就是说,jackson会从JSON内容中处理和删除类型标识符再传递给JsonDeserializer
作用于类/接口,用来列出给定类的子类,只有当子类类型无法被检测到时才会使用它,一般是配合@JsonTypeInfo在基类上使用
The end.