当前位置: 首页 > news >正文

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.

相关文章:

  • 视频监控中的存储方式有哪些?EasyCVR视频监控汇聚平台如何打造高效监控存储
  • 【STM32】ST-Link V2.1制作
  • 【CF】Day62——Codeforces Round 948 (Div. 2) CD (思维 + LCM + 枚举因数 | 思维 + 哈希)
  • Amazon Q 从入门到精通 – 测试与重构
  • Python的传参过程的小细节
  • k8s1.27集群部署mysql8.0双主双从
  • 第二道re
  • UE 材质基础第二天
  • 线光谱共焦传感器:复杂材质检测
  • 【盈达科技】GEO优化实战策略
  • 基于PetaLinux的Zynq PS应用自启动全攻略
  • 浙江大学python程序设计(陈春晖、翁恺、季江民)习题答案-第五章
  • 大模型(1)——基本概念
  • 达梦数据库对json字段进行操作
  • C++开源库argh使用教程
  • Qt Widgets模块功能详细说明,基本控件:QCheckBox(三)
  • JAVA Web 期末速成
  • DeepSeek 赋能机器人研发:从技术革新到场景落地
  • 【C/C++】C语言内存操作与字符串处理汇总
  • 简单实现网页加载进度条
  • 瑞幸首度牵手成都国际非遗节,用一杯饮品将非遗之美推向全国
  • 雷军:小米芯片采用3纳米制程,首款SUV“YU7”即将发布
  • 广东信宜一座在建桥梁暴雨中垮塌,镇政府:未造成人员伤亡
  • 出走的苏敏阿姨一路走到了戛纳,这块红毯因她而多元
  • 从近200件文物文献里,回望光华大学建校百年
  • 新修订的《餐饮业促进和经营管理办法》公布,商务部解读