JSON学习笔记
Jackson
参考
- 用好Jackson,操作Json节省一半时间
- 最棒的Json解析工具Jackson,看这一篇就够了
- Spring Boot框架中使用Jackson的处理总结
写在前面
Jackson 是当前用的比较广泛的,用来序列化和反序列化 json 的 Java 的开源框架。
Jackson 社区相对比较活跃,更新速度也比较快, 从 Github 中的统计来看,Jackson 是最流行的 json 解析器之一 。
Spring MVC 的默认 json 解析器便是 Jackson。
优点
- Jackson 所依赖的 jar 包较少 ,简单易用。
- 与其他 Java 的 json 的框架 Gson 等相比, Jackson 解析大的 json 文件速度比较快;
- Jackson 运行时占用内存比较低,性能比较好;
- Jackson 有灵活的 API,可以很容易进行扩展和定制。
核心模块
- 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-core</artifactId><version>2.9.6</version>
</dependency><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-annotations</artifactId><version>2.9.6</version>
</dependency><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.9.6</version>
</dependency>
ObjectMapper
原理
默认情况下,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注解。
Feature枚举类
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字符串格式没有含有注释符(非标准),然而则经常使用<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);
}
配置
设置序列化时包含的字段
//序列化的时候序列对象的那些属性
//JsonInclude.Include.NON_DEFAULT 属性为默认值不序列化
//JsonInclude.Include.ALWAYS 所有属性
//JsonInclude.Include.NON_EMPTY 属性为 空(“”) 或者为 NULL 都不序列化
//JsonInclude.Include.NON_NULL 属性为NULL 不序列化
mapper.setSerializationInclusion(JsonInclude.Include.ALWAYS);
忽略未知的JSON字段
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
不允许基本类型为null
如果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对象会抛异常。
反序列Json字符串中包含制控制字符
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)
JSON2对象
JSON字符串–>Java对象
ObjectMapper objectMapper = new ObjectMapper();
String carJson = "{ \"brand\" : \"Mercedes\", \"doors\" : 5 }";
Car car = objectMapper.readValue(carJson, Car.class);
JSON 字符输入流–>Java对象
ObjectMapper objectMapper = new ObjectMapper();
String carJson = "{ \"brand\" : \"Mercedes\", \"doors\" : 4 }";
Reader reader = new StringReader(carJson);
Car car = objectMapper.readValue(reader, Car.class);
JSON字节输入流–>Java对象
ObjectMapper objectMapper = new ObjectMapper();
InputStream input = new FileInputStream("data/car.json");
Car car = objectMapper.readValue(input, Car.class);
JSON二进制数组–>Java对象
ObjectMapper objectMapper = new ObjectMapper();
String carJson = "{ \"brand\" : \"Mercedes\", \"doors\" : 5 }";
byte[] bytes = carJson.getBytes("UTF-8");
Car car = objectMapper.readValue(bytes, Car.class);
JSON文件–>Java对象
ObjectMapper objectMapper = new ObjectMapper();
File file = new File("data/car.json");
Car car = objectMapper.readValue(file, Car.class);
JSON via URL—>Java对象
示例使用文件URL,也可以使用HTTP URL
ObjectMapper objectMapper = new ObjectMapper();
URL url = new URL("file:data/car.json");
Car car = objectMapper.readValue(url, Car.class);
JSON数组字符串–>Java对象数组
读取对象数组还可以与字符串以外的其他JSON源一起使用。例如,文件,URL,InputStream,Reader等。
String jsonArray = "[{\"brand\":\"ford\"}, {\"brand\":\"Fiat\"}]";
ObjectMapper objectMapper = new ObjectMapper();
Car[] cars2 = objectMapper.readValue(jsonArray, Car[].class);
JSON数组字符串–>List
String jsonArray = "[{\"brand\":\"ford\"}, {\"brand\":\"Fiat\"}]";
ObjectMapper objectMapper = new ObjectMapper();
List<Car> cars1 = objectMapper.readValue(jsonArray, new TypeReference<List<Car>>(){});
JSON字符串–>Map
如果事先不知道将要解析的确切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>>(){});
对象2JSON
- writeValue()
- writeValueAsString()
- writeValueAsBytes()
objectMapper.writeValue(new FileOutputStream("data/output-2.json"), car);
String json = objectMapper.writeValueAsString(car);
日期转化
Date–>long
默认情况下,Jackson会将java.util.Date对象序列化为其long型的值,该值是自1970年1月1日以来的毫秒数
// transaction对象包含Date类型字段
String output = objectMapper.writeValueAsString(transaction);
// ("type":"transfer","date":1590229541305}
Date–>String
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
objectMapper.setDateFormat(dateFormat);
// transaction对象包含Date类型字段
String output2 = objectMapper.writeValueAsString(transaction);
// {"date":"2020-05-23","type":"transfer"}
全局Default Typing机制
除了使用*@JsonTypeInfo*注解来实现多态数据绑定,还可以使用全局Default Typing机制,开启DefaultTyping就可以让特定类型对象序列化时都存储类型信息,所以反序列时可以知道多态类型的数据的具体类型。
// default to using DefaultTyping.OBJECT_AND_NON_CONCRETE
objectMapper.enableDefaultTyping();
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
DefaultTyping的选项:
- JAVA_LANG_OBJECT: 当对象属性类型为Object时生效
- OBJECT_AND_NON_CONCRETE: 当对象属性类型为Object或者非具体类型(抽象类和接口)时生效
- NON_CONCRETE_AND+_ARRAYS: 同上, 另外所有的数组元素的类型都是非具体类型或者对象类型
- NON_FINAL: 对所有非final类型或者非final类型元素的数组
注解
@JsonProperty
用于属性,把属性的名称序列化时转换为另外一个名称。
@JsonProperty("birth_date")
private Date birth;
@JsonFormat
用于属性或者方法,把属性的格式序列化时转换成指定的格式。
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone="GMT+8")
private LocalDateTime createTime;
@JsonPropertyOrder
用于类, 指定属性在序列化时 json 中的顺序。通常,Jackson会按照在类中找到的顺序序列化PersonPropertyOrder中的属性。
@JsonPropertyOrder({"birth_Date", "name"})
public class Person{}
@JsonSetter
当将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; }
}
@JsonGetter
用于告诉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; }}
@JsonCreator
用于告诉Jackson该Java对象具有一个构造函数,该构造函数可以将JSON对象的字段与Java对象的字段进行匹配。
@JsonCreator注解在无法使用@JsonSetter注解的情况下很有用。例如,不可变对象没有任何设置方法,因此它们需要将其初始值注入到构造函数中。
用于构造方法,和 @JsonProperty 配合使用,适用有参数的构造方法。
{"id" : 1234,"name" : "John"
}
public class PersonImmutable {private long id = 0;private String name = null;@JsonCreatorpublic 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;}}
@JsonAnySetter
用于属性或者方法,设置未反序列化的属性名和值作为键值存储到 map 中,这仅对无法识别的字段有效。
public class Bag {private Map<String, Object> properties = new HashMap<>();@JsonAnySetterpublic 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()方法
@JsonAnyGetter
用于方法 ,获取所有未序列化的属性,可以将Map用作要序列化为JSON的属性的容器
public class PersonAnyGetter {private Map<String, Object> properties = new HashMap<>();@JsonAnyGetterpublic Map<String, Object> properties() {return properties;}
}
properties中的所有键值对都将作为PersonAnyGetter对象的一部分序列化为JSON。
@JsonIgnore
在将JSON读取到Java对象中以及将Java对象写入JSON时,都将忽略该属性。
public class PersonIgnore {@JsonIgnorepublic long personId = 0;public String name = null;
}
@JsonIgnoreProperties
用于指定要忽略的类的属性列表。
@JsonIgnoreProperties({"firstName", "lastName"})
public class PersonIgnoreProperties {public long personId = 0;public String firstName = null;public String lastName = null;}
@JsonIgnoreType
用于将整个类型(类)标记为在使用该类型的任何地方都将被忽略。
public class PersonIgnoreType {@JsonIgnoreTypepublic 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;
}
@JsonAutoDetect
在读写对象时包括非public修饰的属性。
JsonAutoDetect.Visibility类包含与Java中的可见性级别匹配的常量:
- ANY
- DEFAULT
- NON_PRIVATE
- NONE
- PROTECTED_AND_PRIVATE和PUBLIC_ONLY
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY )
public class PersonAutoDetect {private long personId = 123;public String name = null;}
@JsonInclude
仅在某些情况下包括属性。
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class PersonInclude {public long personId = 0;public String name = null;}
@JsonRawValue
告诉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;@JsonRawValuepublic String address = "$#";
}
现在,当对地址属性进行序列化时,jackson将省略引号。因此,序列化的JSON如下所示,它是无效的JSON。
{"personId":0,"address":$#}
如果address属性包含一个JSON字符串,那么该JSON字符串将被序列化为最终的JSON对象,作为JSON对象结构的一部分,而不仅是序列化为JSON对象的address字段中的字符串。
public class PersonRawValue {public long personId = 0;@JsonRawValuepublic 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}"}
@JsonValue
Jackson将在自定义序列化返回的String内转义任何引号
public class PersonValue {public long personId = 0;public String name = null;@JsonValuepublic String toJson(){return this.personId + "," + this.name;}}
要求Jackson序列化PersonValue对象所得到的输出是:
"0,null"
@JsonTypeInfo
简介
对象序列化不会遇到任何挑战。因为不管变量的真实类型是什么,Jackson 总是会通过所有的 getter
方法来找到所有的属性和值,并序列化到 Json 中。
反序列化成对象时,如果是要反序列化成抽象类的某个子类,因为程序不知道JSON对应是哪个子类,所以会抛异常。
当抽象类或者接口需要序列化和反序列化时,需要用到@JsonTypeInfo
注解开启多态类型处理。这个注解可以用到属性上,也可以用到类上,影响的只是作用范围,对效果没什么影响。
属性说明
以下是@JsonTypeInfo的属性说明:
use:定义使用哪一种类型识别码,它有下面几个可选值:
- JsonTypeInfo.Id.CLASS:使用完全限定类名做识别
- JsonTypeInfo.Id.MINIMAL_CLASS:若基类和子类在同一包类,使用类名(忽略包名)作为识别码
- JsonTypeInfo.Id.NAME:一个合乎逻辑的指定名称
- JsonTypeInfo.Id.CUSTOM:自定义识别码
- JsonTypeInfo.Id.NONE:不使用识别码
include(可选):指定识别码是如何被包含进去的,它有下面几个可选值:
- JsonTypeInfo.As.PROPERTY:作为数据的兄弟属性
- JsonTypeInfo.As.EXISTING_PROPERTY:作为POJO中已经存在的属性
- JsonTypeInfo.As.EXTERNAL_PROPERTY:作为扩展属性
- JsonTypeInfo.As.WRAPPER_OBJECT:作为一个包装的对象
- JsonTypeInfo.As.WRAPPER_ARRAY:作为一个包装的数组
property(可选):制定识别码的属性名称,此属性只有满足以下两个条件才生效
- use为
- JsonTypeInfo.Id.CLASS(若不指定property则默认为@class)
- JsonTypeInfo.Id.MINIMAL_CLASS(若不指定property则默认为@c)
- JsonTypeInfo.Id.NAME(若不指定property默认为@type)
- include为
- JsonTypeInfo.As.PROPERTY
- JsonTypeInfo.As.EXISTING_PROPERTY
- JsonTypeInfo.As.EXTERNAL_PROPERTY
defaultImpl(可选):如果类型识别码不存在或者无效,可以使用该属性来制定反序列化时使用的默认类型
visible(可选,默认为false):propery中的属性是否反序列化到POJO中属性定义了类型标识符的值是否会通过JSON流成为反序列化器的一部分,默认为fale,也就是说,jackson会从JSON内容中处理和删除类型标识符再传递给JsonDeserializer
注意
- 当@JsonTypeInfo在属性(字段,方法)上使用时,此注解适用于值。
- 当在集合类型(List,Map,Array)上使用时,它将应用于元素,而不是集合本身。 对于非集合类型,没有区别。
@JsonSubTypes
作用于类/接口,用来列出给定类的子类,只有当子类类型无法被检测到时才会使用它,一般是配合@JsonTypeInfo在基类上使用
The end.