使用 Manifold 解析 JSON
一、簡介
在本文中,我們將了解用於 JSON 文件的 JSON 模式感知解析的Manifold JSON 。我們將了解它是什麼、我們可以用它做什麼以及如何使用它。
Manifold 是一套編譯器插件,每個插件都提供了一些我們可以在應用程式中使用的高效功能。在本文中,我們將探索使用它根據提供的 JSON 架構文件解析和建立 JSON 文件。
2. 安裝
在使用 Manifold 之前,我們需要確保它可供我們的編譯器使用。最重要的整合是作為 Java 編譯器的插件。我們透過適當配置 Maven 或 Gradle 專案來做到這一點。另外,Manifest也提供了一些IDE的插件,讓我們的開發流程變得更簡單。
2.1. Maven 中的編譯器插件
將 Manifold 整合到 Maven 中的應用程式中需要我們新增依賴項和編譯器插件。它們需要是相同的版本,在撰寫本文時為2024.1.20 。
添加我們的依賴項與任何其他依賴項相同:
<dependency>
<groupId>systems.manifold</groupId>
<artifactId>manifold-json-rt</artifactId>
<version>2024.1.20</version>
</dependency>
新增編譯器插件需要我們在模組中設定maven-compiler-plugin
:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<compilerArgs>
<!-- Configure manifold plugin-->
<arg>-Xplugin:Manifold</arg>
</compilerArgs>
<!-- Add the processor path for the plugin -->
<annotationProcessorPaths>
<path>
<groupId>systems.manifold</groupId>
<artifactId>manifold-json</artifactId>
<version>2024.1.20</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
在這裡,我們在執行編譯器時新增-Xplugin:Manifold
命令列參數,並新增 Manifold JSON 註解處理器。
此註解處理器負責從 JSON 模式檔案產生程式碼的所有工作。
2.2. Gradle 中的編譯器插件
使用 Gradle 將 Manifold 整合到我們的應用程式中需要實現相同的效果,但配置稍微簡單一些。 Gradle 支援以與任何其他相依性相同的方式為 Java 編譯器新增註解處理器:
dependencies {
implementation 'systems.manifold:manifold-json-rt:2024.1.20'
annotationProcessor 'systems.manifold:manifold-json:2024.1.20'
testAnnotationProcessor 'systems.manifold:manifold-json:2024.1.20'
}
但是,我們仍然需要確保-Xplugin:Manifold
參數傳遞給編譯器:
tasks.withType(JavaCompile) {
options.compilerArgs += ['-Xplugin:Manifold']
}
此時,我們已準備好開始使用 Manifold JSON。
2.3. IDE插件
除了 Maven 或 Gradle 建置中的插件之外,Manifold 還提供 IntelliJ 和 Android Studio 插件。這些可以從標準插件市場安裝:
安裝後,當從 IDE 中編譯程式碼時,這將允許使用我們先前在專案中設定的編譯器插件,而不是僅依賴 Maven 或 Gradle 建置來執行正確的操作。
3. 使用 JSON Schema 定義類
一旦我們在專案中設定了 Manifold JSON,我們就可以使用它來定義類別。我們透過在src/main/resources
(或src/test/resources
)目錄中編寫 JSON 架構檔案來實現此目的。檔案的完整路徑將成為完全限定的類別名稱。
例如,我們可以透過在src/main/resources/com/baeldung/manifold/SimpleUser.json
中編寫 JSON 模式來建立com.baeldung.manifold.SimpleUser
類別:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "http://baeldung.com/manifold/SimpleUser.json",
"type": "object",
"properties": {
"username": {
"type": "string",
"maxLength": 100
},
"name": {
"type": "string",
"maxLength": 80
},
"email": {
"type": "string",
"format": "email",
"maxLength": 260
}
},
"required": [
"username",
"name"
]
}
這表示一個具有三個欄位的類別 - username
、 name,
和email
- 所有欄位均為String
類型。
然而,Manifold 不會做任何它不需要做的事情。因此,如果沒有任何內容引用該文件,則編寫該文件將不會產生任何程式碼。這個引用可以是編譯器需要的任何東西——甚至是像變數定義這樣簡單的東西。
4. 實例化流形類
一旦我們有了類別定義,我們就希望能夠使用它。然而,我們很快就會發現這些不是常規課程。 Manifold 將我們期望的所有類別產生為 Java 介面。這意味著我們不能簡單地使用new
來建立新實例。
但是,產生的程式碼提供了一個靜態create()
方法,我們可以將其用作建構函數方法。此方法將需要按照在required
數組中指定的順序來取得所有所需值。例如,上面的 JSON Schema 將產生以下內容:
public static SimpleUser create(String username, String name);
然後,我們可以使用 setter 來填入任何剩餘欄位。
因此,我們可以使用以下命令來建立SimpleUser
類別的新實例:
SimpleUser user = SimpleUser.create("testuser", "Test User");
user.setEmail("[email protected]");
除此之外,Manifold 也為我們提供了一個build()
方法,我們可以將其用於建構器模式。這採用相同的一組必需參數,但返回一個構建器對象,我們可以使用它來填充任何其他字段:
SimpleUser user = SimpleUser.builder("testuser", "Test User")
.withEmail("[email protected]")
.build();
如果我們使用可選且只讀的字段,那麼建構器模式是我們提供值的唯一方式 - 一旦我們建立了實例,我們就無法再設定這些字段的值。
5. 解析JSON
一旦我們產生了類別,我們就可以使用它們與 JSON 來源進行互動。我們產生的類別提供了從各種類型的輸入載入資料並將其直接解析到我們的目標類別中的方法。
始終使用我們產生的類別上的靜態load()
方法觸發此路徑。這為我們提供了manifold.json.rt.api.Loader<>
接口,它為我們提供了載入JSON資料的各種方法。
其中最簡單的是能夠解析以某種方式提供給我們的 JSON 字串。這是使用fromJson()
方法完成的,該方法接受一個字串並傳回我們類別的完整形式的實例:
SimpleUser user = SimpleUser.load().fromJson("""
{
"username": "testuser",
"name": "Test User",
"email": "[email protected]"
}
""");
一旦成功,這就會為我們帶來我們想要的結果。失敗時,我們會得到一個RuntimeException
,它包裝了一個manifold.rt.api.ScriptException
,準確指示出了什麼問題。
我們還有各種其他方式提供數據:
-
fromJsonFile()
從java.io.File.
-
fromJsonReader()
從java.io.Reader.
-
fromJsonUrl()
從 URL 讀取資料-在這種情況下,Manifold 將取得該 URL 指向的資料。
但請注意, fromJsonUrl()
方法最終使用java.net.URL.openStream()
來讀取數據,這可能不如我們希望的那麼高效。
所有這些都以相同的方式運作——我們使用適當的資料來源來呼叫它們,它會傳回一個完整的對象,或者如果無法解析資料則拋出RuntimeException
:
InputStream is = getClass().getResourceAsStream("/com/baeldung/manifold/simpleUserData.json");
InputStreamReader reader = new InputStreamReader(is);
SimpleUser user = SimpleUser.load().fromJsonReader(reader);
6. 產生JSON
除了能夠將 JSON 解析為我們的物件之外,我們還可以採用其他方式從我們的物件產生 JSON。
就像 Manifold 產生load()
方法將 JSON 載入到我們的物件中一樣,我們也有一個write()
方法從我們的物件寫入 JSON。這會傳回一個manifold.json.rt.api.Writer
的實例,它為我們提供了從物件寫入JSON 的方法。
其中最簡單的是toJson()
方法,它以String
形式傳回 JSON,我們可以從中執行任何我們想要的動作:
SimpleUser user = SimpleUser.builder("testuser", "Test User")
.withEmail("[email protected]")
.build();
String json = user.write().toJson();
或者,我們可以將 JSON 寫入任何實作Appendable
介面的東西。其中包括java.io.Writer
介面或StringBuilder
和StringBuffer
類別:
SimpleUser user = SimpleUser.builder("testuser", "Test User")
.withEmail("[email protected]")
.build();
user.write().toJson(writer);
然後,該編寫器可以寫入任何目標 - 包括記憶體緩衝區、檔案或網路連線。
7. 替代格式
manifold-json
的主要目的是能夠解析和產生 JSON 內容。但是,我們也支援其他格式 - CSV、XML 和 YAML。
對於其中每一個,我們都需要在我們的專案中添加特定的依賴項——systems.manifold systems.manifold:manifold-csv-rt
(針對 CSV)、 systems.manifold:manifold-xml-rt
(針對 XML)或systems.manifold:manifold-yaml-rt
(針對 CSV) YAML。我們可以根據需要添加任意數量的這些。
完成此操作後,我們可以在 Manifold 提供給我們的Loader
和Writer
介面上使用適當的方法:
SimpleUser user = SimpleUser.load().fromXmlReader(reader);
String yaml = user.write().toYaml();
8. JSON 模式特性
Manifold 使用 JSON 模式檔案來描述我們的類別的結構。我們可以使用這些文件來描述要產生的類別以及其中存在的欄位。但是,我們可以使用 JSON Schema 來描述更多功能,並且 Manifold JSON 支援其中一些額外功能。
8.1.只讀和只寫字段
在架構中標記為唯讀的欄位是在沒有設定器的情況下產生的。這意味著我們可以在構造時或透過解析輸入檔來填充它們,但之後永遠不能更改值:
"username": {
"type": "string",
"readOnly": true
},
相反,生成器在模式中建立標記為只寫的字段,而無需獲取器。這意味著我們可以按照自己的意願填充它們——在建置時、從輸入檔案解析或使用設定器——但我們永遠無法取回值。
"mfaCode": {
"type": "string",
"writeOnly": true
},
請注意,系統仍然在任何產生的輸出中呈現只寫屬性,因此我們可以透過該路徑存取它們。但是,我們無法從 Java 類別中讀取這些屬性。
8.2.格式化類型
JSON 模式中的某些類型允許我們提供附加格式資訊。例如,我們可以指定欄位是string
,但格式為date-time
。這是對使用我們應該在這些欄位中看到的資料類型模式的任何內容的提示。
Manifold JSON 將盡力理解這些格式並為它們產生適當的 Java 類型。例如,格式化為date-time
的string
將在我們的程式碼中產生為java.time.LocalDateTime
。
8.3.附加屬性
JSON Schema 允許我們透過使用additionalProperties
和patternProperties
來定義開放式模式。
additionalProperties
標誌指示該類型可以具有任意數量的任何類型的額外屬性。本質上,這意味著架構允許任何其他 JSON 匹配。 Manifold JSON 預設將此定義為true
,但如果我們願意,我們可以在模式中將其明確設為false
。
如果將此設為 true,則 Manifold 將為我們產生的類別提供兩個附加方法 - get(name)
和put(name, value)
。使用這些我們可以處理我們希望的任何任意字段:
SimpleUser user = SimpleUser.builder("testuser", "Test User")
.withEmail("[email protected]")
.build();
user.put("note", "This isn't specified in the schema");
請注意,沒有對這些值進行驗證。這包括檢查名稱是否與其他定義的欄位衝突。因此,這可以用來覆寫我們模式中定義的欄位 - 包括忽略驗證規則,例如type
或readOnly
。
JSON Schema 也支援一個更高階的概念,稱為「模式屬性」。我們將它們定義為完整的屬性定義,但我們使用正規表示式來定義屬性名稱而不是固定字串。例如,此定義將允許我們將字段note0
到note9
全部指定為string:
"patternProperties": {
"note[0-9]": {
"type": "string"
}
}
Manifold JSON 對此有部分支援。它不會產生顯式程式碼來處理此問題,而是將類型中任何patternProperties
的存在視為與additionalProperties
設為true
相同。這包括我們是否明確將additionalProperties
設定為false
。
8.4.巢狀類型
JSON Schema 支援我們定義一種嵌套在另一種類型中的類型,而不需要在頂層定義所有內容並使用參考。這對於為 JSON 提供本地化結構非常有用。例如:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "http://baeldung.com/manifold/User.json",
"type": "object",
"properties": {
"email": {
"type": "object",
"properties": {
"address": {
"type": "string",
"maxLength": 260
},
"verified": {
"type": "boolean"
}
},
"required": ["address", "verified"]
}
}
}
這裡我們定義了一個對象,其中一個欄位是另一個對象。然後我們用 JSON 來表示它,如下所示:
{
"email": {
"address": "[email protected]",
"verified": false
}
}
Manifold JSON 對此完全支持,並將自動產生適當命名的內部類別。然後我們可以像使用其他類別一樣使用這些類別:
User user = User.builder()
.withEmail(User.email.builder("[email protected]", false).build())
.build();
8.5。作品
JSON Schema 支援使用allOf
、 anyOf,
或oneOf
關鍵字將不同類型組合在一起。其中每一個都採用其他模式的集合(透過引用或直接指定),並且產生的模式必須分別匹配所有這些模式,至少匹配其中一個,或恰好匹配其中一個。 Manifold JSON 對這些關鍵字有一定程度的支援。
當使用allOf
時,Manifold 將產生一個包含組合類型的所有定義的類別。如果我們內聯定義這些,系統將建立一個包含所有定義的類別:
"allOf": [
{
"type": "object",
"properties": {
"username": {
"type": "string"
}
}
},
{
"type": "object",
"properties": {
"roles": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
]
或者,如果我們使用引用進行組合,生成的介面將擴展我們引用的所有類型:
"allOf": [
{"$ref": "#/definitions/user"},
{"$ref": "#/definitions/adminUser"}
]
在這兩種情況下,我們都可以使用生成的類,就好像它符合兩組定義一樣:
Composed.user.builder()
.withUsername("testuser")
.withRoles(List.of("admin"))
.build()
相反,如果我們使用anyOf
或oneOf
,那麼Manifold將產生可以以類型安全的方式接受替代選項的程式碼。這需要我們使用引用,以便 Manifold 可以推斷類型名稱:
"anyOf": [
{"$ref": "#/definitions/Dog"},
{"$ref": "#/definitions/Cat"}
]
這樣做時,我們的類型獲得了與不同選項互動的類型安全方法:
Composed composed = Composed.builder()
.withAnimalAsCat(Composed.Cat.builder()
.withColor("ginger")
.build())
.build();
assertEquals("ginger", composed.getAnimalAsCat().getColor());
在這裡我們可以看到我們的建構器獲得了一個withAnimalAsCat
方法 - 其中「 Animal
」部分是外部物件中的欄位名稱,「 at
」部分是從我們的定義中推斷出的類型名稱。我們的實際物件也以同樣的方式獲得了getAnimalAsCat
和setAnimalAsCat
方法。
9. 結論
在本文中,我們對 Manifold JSON 進行了廣泛的介紹。這個庫可以做更多的事情,所以為什麼不嘗試看看呢?
所有範例都可以在 GitHub 上取得。