Java XML和JSON:Java SE的文件處理 第2部分
在這篇文章中,我們將繼續探索Java 11及更高版本中的XML和JSON。
本文中的示例將向您介紹JSON-B,JSON繫結API for Java 。在快速概述和安裝說明之後,我將向您展示如何使用JSON-B來序列化和反序列化Java物件,陣列和集合; 如何使用JSON-B自定義序列化和反序列化; 以及如何在序列化或反序列化期間使用JSON-B介面卡將源物件轉換為目標物件。
這篇文章的材料是全新的,但可以被認為是我的新書的另一章(第13章),最近由Apress出版: Java XML和JSON,第二版 。
什麼是JSON-B?
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-B API
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
JSON-B 1.0是撰寫本文時的當前版本。您可以從Maven儲存庫 獲取此庫的Yasson參考實現。您需要下載以下JAR檔案:
-
Javax JSON Bind API 1.0
:包含所有JSON-B類檔案。我下載了
javax.json.bind-api-1.0.jar
。 -
Yasson
:包含基於Eclipse的JSON-B參考實現。我下載了
yasson-1.0.3.jar
。 -
JSR 374(JSON處理)預設提供程式
:包含所有JSON-P 1.0類檔案以及Glassfish預設提供程式類檔案。我下載了
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
使用JSON-B序列化和反序列化Java物件
該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展示了此演示的原始碼。
清單1. JSONBDemo.java(版本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
。
清單2. Employee.java(版本1)
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]
使用JSON-B的規則
在玩這個應用程式時,我觀察到一些有趣的行為,這些行為使我制定了以下有關Employee
的規則:
-
class必須是
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規範
以獲取支援型別的完整列表。
使用JSON-B序列化和反序列化陣列和集合
上一節重點介紹了單個Java物件的序列化和反序列化。JSON-B還支援序列化和反序列化物件陣列和集合的功能。清單3提供了一個演示。
清單3. JSONBDemo.java(版本2)
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中自定義序列化和反序列化
雖然JSON-B通過支援各種Java型別為您做了很多事情,但您可能需要自定義其行為; 例如,更改序列化屬性的輸出順序。JSON-B支援編譯時和執行時自定義。
編譯時自定義
JSON-B通過其javax.json.bind.annotation
包中的各種註釋型別支援編譯時自定義。例如,您可以使用JsonbDateFormat
提供自定義日期格式並更改JsonbProperty
欄位的名稱。清單4的Employee
類中說明了這兩種註釋型別。
清單4. Employee.java(版本2)
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。
清單5. JSONBDemo.java(版本3)
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中使用介面卡
最後,JSON-B支援介面卡 ,它是在序列化或反序列化期間將源物件轉換為目標物件的物件。例如,您可以使用介面卡來加密JSON文件中物件的欄位名稱和值。
介面卡由原始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
,一個不會改變任何東西的介面卡。但是,它會打印出原本可以調整的物件,並演示介面卡架構。
清單6. IdentityAdapter.java
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
原始碼演示了這兩個任務。
清單7. JSONBDemo.java(版本4)
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並向您展示瞭如何使用它來序列化和反序列化Java物件,陣列和集合。我還向您展示瞭如何使用JSON-B自定義序列化和反序列化,並向您介紹了JSON-B介面卡,它們可用於在序列化或反序列化期間將源物件轉換為目標物件。
我確信JSON-B將繼續發展,並且可能是我書第三版的一個很好的補充。同時,我建議您通過探索本文未涉及的各種方法和註釋型別來了解有關JSON-B的更多資訊。