在这篇文章中,我们将继续探索Java 11及更高版本中的XML和JSON。
本文中的示例将向您介绍JSON-B,JSON绑定API for Java。在快速概述和安装说明之后,我将向您展示如何使用JSON-B来序列化和反序列化Java对象,数组和集合; 如何使用JSON-B自定义序列化和反序列化; 以及如何在序列化或反序列化期间使用JSON-B适配器将源对象转换为目标对象。
这篇文章的材料是全新的,但可以被认为是我的新书的另一章(第13章),最近由Apress出版:
Java XML和JSON,第二版
。
JSON-B
是一个标准的绑定层和API,用于将Java对象与JSON文档进行转换。它类似于XML绑定的Java体系结构(JAXB),它用于将Java对象转换为XML或从XML转换成Java对象。
JSON-B构建于JSON-P之上,JSON-P是用于解析,生成,查询和转换JSON文档的JSON处理API。JSON-B是由 Java规范请求(JSR)367 在JSR 353(JSR for JSON-P)最终发布一年多之后推出的。
JSON绑定 的Java API(JSON-B)网站引入了JSON-B并提供对各种资源的访问,包括API文档。根据文档,JSON-B模块存储了六个包:
javax.json.bind
:定义将Java对象绑定到JSON文档的入口点。 javax.json.bind.adapter
:定义与适配器相关的类。 javax.json.bind.annotation
:定义用于自定义Java程序元素和JSON文档之间的映射的注释。 javax.json.bind.config
:定义用于自定义Java程序元素和JSON文档之间的映射的策略和策略。 javax.json.bind.serializer
:定义用于创建自定义序列化程序和反序列化程序的接口。 javax.json.bind.spi
:定义用于插入自定义的服务提供者接口(SPI) JsonbBuilder
。 JSON-B网站还提供了Yasson的链接,Yasson是一个Java框架,提供Java类和JSON文档之间的标准绑定层,以及JSON Binding API的官方参考实现。
JSON-B 1.0是撰写本文时的当前版本。您可以从Maven存储库获取此库的Yasson参考实现。您需要下载以下JAR文件:
javax.json.bind-api-1.0.jar
。 yasson-1.0.3.jar
。 javax.json-1.1.4.jar
。 在编译和运行使用这些库的代码时,将这些JAR文件添加到类路径中:
javac -cp javax.json.bind-api-1.0.jar;. main source file java -cp javax.json.bind-api-1.0.jar;yasson-1.0.3.jar;javax.json-1.1.4.jar;. main classfile复制代码
该 javax.json.bind
包提供了 Jsonb
和 JsonbBuilder
接口,它们充当此库的入口点:
Jsonb
提供了 toJson()
用于将Java对象的树序列化为JSON文档的重载方法,以及 fromJson()
用于将JSON文档反序列化为Java对象树的方法。 JsonbBuilder
提供 newBuilder()
和其他方法获得新的构建,并 build()
和 create()
返回新方法 Jsonb
的对象。
以下代码示例演示了 Jsonb
和 JsonBuilder
类型的基本用法:
// Create a new Jsonb instance using the default JsonbBuilder implementation. Jsonb jsonb = JsonbBuilder.create(); // Create an Employee object from a hypothetical Employee class. Employee employee = ... // Convert the Employee object to a JSON document stored in a string. String jsonEmployee = jsonb.toJson(employee); // Convert the previously-created JSON document to an Employee object. Employee employee2 = jsonb.fromJson(jsonEmployee, Employee.class);复制代码
此示例调用序列化Java对象 Jsonb
的 String toJson(Object object)
方法( Employee
)。此方法传递给Java对象树的根目录以进行序列化。如果 null
通过,则 toJson()
抛出 java.lang.NullPointerException
。在序列化期间发生意外问题(例如I / O错误)时,它会抛出 javax.json.bind.JsonbException
。
此代码段还调用 Jsonb
的 <T> T fromJson(String str, Class<T> type)
通用方法,该方法被用于反序列化。此方法传递基于字符串的JSON文档以反序列化,并返回生成的Java对象树的根对象的类型。传递给此方法任一参数为 null
时抛出 NullPointerException
; 在反序列化期间发生意外问题时抛出 JsonbException
。
我从一个 JSONBDemo
提供JSON-B基本演示的应用程序中摘录了代码片段。清单1展示了此演示的源代码。
import java.time.LocalDate; import javax.json.bind.Jsonb; import javax.json.bind.JsonbBuilder; public class JSONBDemo { public static void main(String[] args) { Jsonb jsonb = JsonbBuilder.create(); Employee employee = new Employee("John", "Doe", 123456789, false, LocalDate.of(1980, 12, 23), LocalDate.of(2002, 8, 14)); String jsonEmployee = jsonb.toJson(employee); System.out.println(jsonEmployee); System.out.println(); Employee employee2 = jsonb.fromJson(jsonEmployee, Employee.class); System.out.println(employee2); } }复制代码
main()
首先创建一个 Jsonb
对象,后跟一个 Employee
对象。然后,它调用 toJson()
将 Employee
对象序列化为存储在字符串中的JSON文档。打印该文档后, main()
调用 fromJson()
与把字符串反序列化为 Employee
。
import java.time.LocalDate; public class Employee { private String firstName; private String lastName; private int ssn; private boolean isMarried; private LocalDate birthDate; private LocalDate hireDate; private StringBuffer sb = new StringBuffer(); public Employee() {} public Employee(String firstName, String lastName, int ssn, boolean isMarried, LocalDate birthDate, LocalDate hireDate) { this.firstName = firstName; this.lastName = lastName; this.ssn = ssn; this.isMarried = isMarried; this.birthDate = birthDate; this.hireDate = hireDate; } public String getFirstName() { return firstName; } public String getLastName() { return lastName; } public int getSSN() { return ssn; } public boolean isMarried() { return isMarried; } public LocalDate getBirthDate() { return birthDate; } public LocalDate getHireDate() { return hireDate; } public void setFirstName(String firstName) { this.firstName = firstName; } public void setLastName(String lastName) { this.lastName = lastName; } public void setSSN(int ssn) { this.ssn = ssn; } public void setIsMarried(boolean isMarried) { this.isMarried = isMarried; } public void setBirthDate(LocalDate birthDate) { this.birthDate = birthDate; } public void setHireDate(LocalDate hireDate) { this.hireDate = hireDate; } @Override public String toString() { sb.setLength(0); sb.append("First name ["); sb.append(firstName); sb.append("], Last name ["); sb.append(lastName); sb.append("], SSN ["); sb.append(ssn); sb.append("], Married ["); sb.append(isMarried); sb.append("], Birthdate ["); sb.append(birthDate); sb.append("], Hiredate ["); sb.append(hireDate); sb.append("]"); return sb.toString(); } }复制代码
编制清单1和2如下:
javac -cp javax.json.bind-api-1.0.jar;. JSONBDemo.java复制代码
运行应用程序如下:
java -cp javax.json.bind-api-1.0.jar;yasson-1.0.3.jar;javax.json-1.1.4.jar;. JSONBDemo复制代码
您应该观察以下输出(为了便于阅读,分布在多行中):
{"SSN":123456789,"birthDate":"1980-12-23","firstName":"John","hireDate":"2002-08-14", "lastName":"Doe","married":false} First name [John], Last name [Doe], SSN [123456789], Married [false], Birthdate [1980-12-23], Hiredate [2002-08-14] 复制代码
在玩这个应用程序时,我观察到一些有趣的行为,这些行为使我制定了以下有关 Employee
的规则:
public
; 否则,抛出异常。 toJson()
不会使用非 public
getter方法序列化字段。 fromJson()
不会使用非 public
setter方法反序列化字段。 fromJson()
在没有 public no argument
构造函数的情况下抛出 JsonbException
。 为了在Java对象字段和JSON数据之间无缝转换,JSON-B必须支持各种Java类型。例如,JSON-B支持以下基本Java类型:
java.lang.Boolean java.lang.Byte java.lang.Character java.lang.Double java.lang.Float java.lang.Integer java.lang.Long java.lang.Short java.lang.String
其他类型,例如 java.math.BigInteger
, java.util.Date
和 java.time.LocalDate
支持。查看JSON-B规范以获取支持类型的完整列表。
上一节重点介绍了单个Java对象的序列化和反序列化。JSON-B还支持序列化和反序列化对象数组和集合的功能。清单3提供了一个演示。
import java.time.LocalDate; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import javax.json.bind.Jsonb; import javax.json.bind.JsonbBuilder; public class JSONBDemo { public static void main(String[] args) { arrayDemo(); listDemo(); } // Serialize and deserialize an array of Employee objects. static void arrayDemo() { Jsonb jsonb = JsonbBuilder.create(); Employee[] employees = { new Employee("John", "Doe", 123456789, false, LocalDate.of(1980, 12, 23), LocalDate.of(2002, 8, 14)), new Employee("Jane", "Smith", 987654321, true, LocalDate.of(1982, 6, 13), LocalDate.of(2001, 2, 9)) }; String jsonEmployees = jsonb.toJson(employees); System.out.println(jsonEmployees); System.out.println(); employees = null; employees = jsonb.fromJson(jsonEmployees, Employee[].class); for (Employee employee: employees) { System.out.println(employee); System.out.println(); } } // Serialize and deserialize a List of Employee objects. static void listDemo() { Jsonb jsonb = JsonbBuilder.create(); List<Employee> employees = Arrays.asList(new Employee("John", "Doe", 123456789, false, LocalDate.of(1980, 12, 23), LocalDate.of(2002, 8, 14)), new Employee("Jane", "Smith", 987654321, true, LocalDate.of(1982, 6, 13), LocalDate.of(1999, 7, 20))); String jsonEmployees = jsonb.toJson(employees); System.out.println(jsonEmployees); System.out.println(); employees = null; employees = jsonb.fromJson(jsonEmployees, new ArrayList<>(){}. getClass().getGenericSuperclass()); System.out.println(employees); } }复制代码
清单3是表1的简单扩展,并使用相同的 Employee
清单2.此外,在呈现类,此代码示例和 toJson()
和 fromJson()
方法调用相同。
将JSON文档反序列化为Java对象数组时,将表达式 Employee[].class
作为第二个参数传递给 fromJson()
,以便它可以创建适当的数组。将JSON对象反序列化为列表或其他集合时,会将表达式 new ArrayList<>(){}.getClass().getGenericSuperclass()
作为第二个参数传递。JDK 11会推断 Employee
,所以我不必指定 ArrayList<Employee>
。
理想情况下,应该可以传递 ArrayList<Employee>.class
,以告知 fromJson()
集合的预期参数化类型进行实例化。但是,由于类型擦除,这种表达是非法的。相反,我可以指定 ArrayList.class
哪个会起作用。但是,它还会生成未经检查的警告消息。表达式越复杂,就不会产生警告。本质上,它实例化一个匿名子类 ArrayList<Employee>
,获取它的 Class
对象,并使用该 Class
对象来获取其超类的参数化类型,这恰好是 ArrayList<Employee>
。此参数化类型可用于 fromJson()
。
编译清单3和2,并运行生成的应用程序。您应该观察以下输出(为了便于阅读,分布在多行中):
[{"SSN":123456789,"birthDate":"1980-12-23","firstName":"John","hireDate":"2002-08-14", "lastName":"Doe","married":false}, {"SSN":987654321,"birthDate":"1982-06-13","firstName":"Jane","hireDate":"2001-02-09", "lastName":"Smith","married":true}] First name [John], Last name [Doe], SSN [123456789], Married [false], Birthdate [1980-12-23], Hiredate [2002-08-14] First name [Jane], Last name [Smith], SSN [987654321], Married [false], Birthdate [1982-06-13], Hiredate [2001-02-09] [{"SSN":123456789,"birthDate":"1980-12-23","firstName":"John","hireDate":"2002-08-14", "lastName":"Doe","married":false}, {"SSN":987654321,"birthDate":"1982-06-13","firstName":"Jane","hireDate":"1999-07-20", "lastName":"Smith","married":true}] [{firstName=John, lastName=Doe, hireDate=2002-08-14, birthDate=1980-12-23, married=false, SSN=123456789}, {firstName=Jane, lastName=Smith, hireDate=1999-07-20, birthDate=1982-06-13, married=true, SSN=987654321}] 复制代码
虽然JSON-B通过支持各种Java类型为您做了很多事情,但您可能需要自定义其行为; 例如,更改序列化属性的输出顺序。JSON-B支持编译时和运行时自定义。
JSON-B通过其 javax.json.bind.annotation
包中的各种注释类型支持编译时自定义。例如,您可以使用 JsonbDateFormat
提供自定义日期格式并更改 JsonbProperty
字段的名称。清单4的 Employee
类中说明了这两种注释类型。
import java.time.LocalDate; import javax.json.bind.annotation.JsonbDateFormat; import javax.json.bind.annotation.JsonbProperty; public class Employee { @JsonbProperty("first-name") private String firstName; @JsonbProperty("last-name") private String lastName; private int ssn; private boolean isMarried; @JsonbDateFormat("MM-dd-yyyy") private LocalDate birthDate; @JsonbDateFormat("MM-dd-yyyy") private LocalDate hireDate; private StringBuffer sb = new StringBuffer(); public Employee() {} public Employee(String firstName, String lastName, int ssn, boolean isMarried, LocalDate birthDate, LocalDate hireDate) { this.firstName = firstName; this.lastName = lastName; this.ssn = ssn; this.isMarried = isMarried; this.birthDate = birthDate; this.hireDate = hireDate; } public String getFirstName() { return firstName; } public String getLastName() { return lastName; } public int getSSN() { return ssn; } public boolean isMarried() { return isMarried; } public LocalDate getBirthDate() { return birthDate; } public LocalDate getHireDate() { return hireDate; } public void setFirstName(String firstName) { this.firstName = firstName; } public void setLastName(String lastName) { this.lastName = lastName; } public void setSSN(int ssn) { this.ssn = ssn; } public void setIsMarried(boolean isMarried) { this.isMarried = isMarried; } public void setBirthDate(LocalDate birthDate) { this.birthDate = birthDate; } public void setHireDate(LocalDate hireDate) { this.hireDate = hireDate; } @Override public String toString() { sb.setLength(0); sb.append("First name ["); sb.append(firstName); sb.append("], Last name ["); sb.append(lastName); sb.append("], SSN ["); sb.append(ssn); sb.append("], Married ["); sb.append(isMarried); sb.append("], Birthdate ["); sb.append(birthDate); sb.append("], Hiredate ["); sb.append(hireDate); sb.append("]"); return sb.toString(); } }复制代码
清单4用 JsonbProperty
注释 firstName
和 lastName
字段,并用 JsonbDateFormat
注释 birthDate
和 hireDate
字段。 JsonbProperty
导致 firstName
序列化为 first-name
和 lastName
序列化为 last-name
。此注释类型还会导致 first-name
反序列化 firstName
并 last-name
反序列化 lastName
。 JsonbDateFormat
导致生日和雇用日期在月 - 日 - 年中序列化,而不是默认的年 - 月 - 日订单,并导致JSON-B在反序列化时考虑序列化的月 - 日 - 年订单。
编译清单1和清单4,然后运行生成的应用程序。您应该观察以下输出(为了便于阅读,分布在多行中):
{"SSN":123456789,"birthDate":"12-23-1980","first-name":"John","hireDate":"08-14-2002", "last-name":"Doe","married":false} First name [John], Last name [Doe], SSN [123456789], Married [false], Birthdate [1980-12-23], Hiredate [2002-08-14] 复制代码
JSON-B通过 javax.json.bind.JsonbConfig
和 JsonbBuilder
支持运行时自定义。在实例 JsonbConfig
中,调用各种 with
XXX方法(例如, withPropertyOrderStrategy
)来配置该任务,并且使配置 JsonbConfig
提供给对象 JsonBuilder
,可能通过将其作为 JsonbBuilder
的 static Jsonb create(JsonbConfig config)
方法的参数。查看清单5。
import java.time.LocalDate; import javax.json.bind.Jsonb; import javax.json.bind.JsonbBuilder; import javax.json.bind.JsonbConfig; import static javax.json.bind.config.PropertyOrderStrategy.*; public class JSONBDemo { public static void main(String[] args) { JsonbConfig config = new JsonbConfig() .withPropertyOrderStrategy(REVERSE); Jsonb jsonb = JsonbBuilder.create(config); Employee employee = new Employee("John", "Doe", 123456789, false, LocalDate.of(1980, 12, 23), LocalDate.of(2002, 8, 14)); String jsonEmployee = jsonb.toJson(employee); System.out.println(jsonEmployee); System.out.println(); Employee employee2 = jsonb.fromJson(jsonEmployee, Employee.class); System.out.println(employee2); } }复制代码
清单5的 main()
方法首先实例化 JsonbConfig
然后调用此类的 JsonbConfig withPropertyOrderStrategy(String propertyOrderStrategy)
方法将属性顺序策略更改为 javax.json.bind.config.PropertyOrderStrategy.REVERSE
。此策略顺序导致属性以与正常输出方式相反的顺序输出。
该 JsonbConfig
对象被传递给 create(JsonbConfig)
配置所得到的 Jsonb
对象 JsonbBuilder
最终返回。该方法的其余部分与清单1中所示的相同。
编译清单2和5,然后运行生成的应用程序。您应该观察以下输出(为了便于阅读,分布在多行中):
{"married":false,"lastName":"Doe","hireDate":"2002-08-14","firstName":"John", "birthDate":"1980-12-23","SSN":123456789} First name [John], Last name [Doe], SSN [123456789], Married [false], Birthdate [1980-12-23], Hiredate [2002-08-14] 复制代码
您可以使用JSON-B的注释类型之一完成相同的反向属性顺序任务。我会留下弄清楚如何做这个练习。
最后,JSON-B支持
适配器
适配器由原始Java对象,包含修改/附加字段的适配/转换对象和适配器对象组成,适配器对象是该 javax.json.bind.adapter.Adapter<Original,Adapted>
类型的实例。
该 Adapter
类型提供以下方法:
Original adaptFromJson(Adapted obj)
:在反序列化期间调用此方法以转换 Adapted
为 Original
。 Adapted adaptToJson(Original obj)
:在序列化期间调用此方法以转换 Original
为 Adapted
,然后将其序列化为JSON。
这两种方法都用一个 throws Exception
子句声明,表明它可以在转换期间抛出任何类型的异常。
清单6给出了一个源代码 IdentityAdapter
,一个不会改变任何东西的适配器。但是,它会打印出原本可以调整的对象,并演示适配器架构。
import javax.json.bind.adapter.JsonbAdapter; public class IdentityAdapter implements JsonbAdapter<Employee, Employee> { @Override public Employee adaptFromJson(Employee obj) { System.out.println("Deserializing: " + obj); return obj; } @Override public Employee adaptToJson(Employee obj) { System.out.println("Serializing: " + obj); return obj; } }复制代码
您使用 JsonbConfig
及其 JsonbConfig withAdapters(JsonbAdapter...)
方法来注册一个或多个适配器:
JsonbConfig config = new JsonbConfig() .withAdapters(new IdentityAdapter());复制代码
这个对象传递给 JsonbBuilder
的 create(JsonbConfig)
方法,正如我前面表现。为了完整起见,清单7的 JSONBDemo
源代码演示了这两个任务。
import java.time.LocalDate; import javax.json.bind.Jsonb; import javax.json.bind.JsonbBuilder; import javax.json.bind.JsonbConfig; public class JSONBDemo { public static void main(String[] args) { JsonbConfig config = new JsonbConfig() .withAdapters(new IdentityAdapter()); Jsonb jsonb = JsonbBuilder.create(config); Employee employee = new Employee("John", "Doe", 123456789, false, LocalDate.of(1980, 12, 23), LocalDate.of(2002, 8, 14)); String jsonEmployee = jsonb.toJson(employee); System.out.println(jsonEmployee); System.out.println(); Employee employee2 = jsonb.fromJson(jsonEmployee, Employee.class); System.out.println(employee2); } }复制代码
编译清单2,6和7,并运行生成的应用程序。您应该观察以下输出(为了便于阅读,分布在多行中):
Serializing: First name [John], Last name [Doe], SSN [123456789], Married [false], Birthdate [1980-12-23], Hiredate [2002-08-14] {"SSN":123456789,"birthDate":"1980-12-23","firstName":"John","hireDate":"2002-08-14", "lastName":"Doe","married":false} Deserializing: First name [John], Last name [Doe], SSN [123456789], Married [false], Birthdate [1980-12-23], Hiredate [2002-08-14] First name [John], Last name [Doe], SSN [123456789], Married [false], Birthdate [1980-12-23], Hiredate [2002-08-14] 复制代码
JSON-B很好地补充了JSON-P,我在本书的第12章,
Java XML和JSON,第二版中介绍了它
我确信JSON-B将继续发展,并且可能是我书第三版的一个很好的补充。同时,我建议您通过探索本文未涉及的各种方法和注释类型来了解有关JSON-B的更多信息。
英文原文: www.javaworld.com/article/335…
查看更多文章:www.apexyun.com
公众号:银河系1号
联系邮箱:public@space-explore.com
(未经同意,请勿转载)