解析和產生 JSON
Groovy 內建支援將 Groovy 物件與 JSON 之間進行轉換。專門用於 JSON 序列化和解析的類別位於 groovy.json
套件中。
1. JsonSlurper
JsonSlurper
是個類別,它會將 JSON 文字或讀取器內容解析成 Groovy 資料結構(物件),例如映射、清單和基本類型,如 Integer
、Double
、Boolean
和 String
。
這個類別附帶許多過載的 parse
方法,以及一些特殊方法,例如 parseText
、parseFile
等。在接下來的範例中,我們將使用 parseText
方法。它會解析 JSON String
,並遞迴地將其轉換為物件的清單或映射。其他 parse*
方法類似,它們會傳回 JSON String
,但參數類型不同。
def jsonSlurper = new JsonSlurper()
def object = jsonSlurper.parseText('{ "name": "John Doe" } /* some comment */')
assert object instanceof Map
assert object.name == 'John Doe'
請注意,結果是一個純粹的映射,可以像處理一般 Groovy 物件實例一樣處理。JsonSlurper
會根據 ECMA-404 JSON 交換標準 定義來解析給定的 JSON,並支援 JavaScript 註解和日期。
除了映射之外,JsonSlurper
還支援 JSON 陣列,這些陣列會轉換為清單。
def jsonSlurper = new JsonSlurper()
def object = jsonSlurper.parseText('{ "myList": [4, 8, 15, 16, 23, 42] }')
assert object instanceof Map
assert object.myList instanceof List
assert object.myList == [4, 8, 15, 16, 23, 42]
JSON 標準支援下列基本資料類型:字串、數字、物件、true
、false
和 null
。JsonSlurper
會將這些 JSON 類型轉換為對應的 Groovy 類型。
def jsonSlurper = new JsonSlurper()
def object = jsonSlurper.parseText '''
{ "simple": 123,
"fraction": 123.66,
"exponential": 123e12
}'''
assert object instanceof Map
assert object.simple.class == Integer
assert object.fraction.class == BigDecimal
assert object.exponential.class == BigDecimal
由於 JsonSlurper
會傳回純粹的 Groovy 物件實例,而沒有任何特殊的 JSON 類別作為後盾,因此它的使用方式很透明。事實上,JsonSlurper
的結果符合 GPath 表達式。GPath 是一種強大的表達式語言,由支援不同資料格式的各種 slurper 所支援(例如 XML 的 XmlSlurper
)。
如需更多詳細資訊,請參閱 GPath 表達式 部分。 |
下表概述了 JSON 類型和對應的 Groovy 資料類型
JSON | Groovy |
---|---|
字串 |
|
數字 |
|
物件 |
|
陣列 |
|
true |
|
false |
|
null |
|
日期 |
|
每當 JSON 中的值為 null 時,JsonSlurper 會補充 Groovy null 值。這與其他 JSON 解析器形成對比,後者使用由程式庫提供的單例物件來表示 null 值。
|
1.1. 解析器變體
JsonSlurper
附帶幾個解析器實作。每個解析器都符合不同的需求,對於某些情況而言,JsonSlurper
預設解析器可能並非所有情況的最佳選擇。以下是附帶的解析器實作概述
-
JsonParserCharArray
解析器基本上會取得一個 JSON 字串,並在基礎字元陣列上執行操作。在值轉換期間,它會複製字元子陣列(一種稱為「切斷」的機制)並在它們上執行操作。 -
JsonFastParser
是JsonParserCharArray
的特殊變體,也是最快的解析器。然而,它並非預設解析器是有原因的。JsonFastParser
是所謂的索引疊加解析器。在解析給定的 JSONString
期間,它會盡可能避免建立新的字元陣列或String
執行個體。它只會保留指向基礎原始字元陣列的指標。此外,它會盡可能延後建立物件。如果已解析的對應放入長期快取中,則必須小心,因為對應物件可能尚未建立,而且仍然只包含指向原始字元緩衝區的指標。然而,JsonFastParser
附帶一種特殊切斷模式,它會提早切塊字元緩衝區,以保留原始緩衝區的一個小副本。建議將JsonFastParser
用於小於 2MB 的 JSON 緩衝區,並記住長期快取限制。 -
JsonParserLax
是JsonParserCharArray
解析器的特殊變體。它具有與JsonFastParser
相似的效能特性,但不同之處在於它並非完全依賴 ECMA-404 JSON 語法。例如,它允許註解、無引號字串等。 -
JsonParserUsingCharacterSource
是專門用於非常大型檔案的解析器。它使用一種稱為「字元視窗」的技術來解析大型 JSON 檔案(在此情況下,大型是指大於 2MB 的檔案),並具有恆定的效能特性。
JsonSlurper
的預設剖析器實作是 JsonParserCharArray
。JsonParserType
列舉包含上述剖析器實作的常數
實作 | 常數 |
---|---|
|
|
|
|
|
|
|
|
只要呼叫 JsonSlurper#setType()
設定 JsonParserType
,就可以輕鬆變更剖析器實作。
def jsonSlurper = new JsonSlurper(type: JsonParserType.INDEX_OVERLAY)
def object = jsonSlurper.parseText('{ "myList": [4, 8, 15, 16, 23, 42] }')
assert object instanceof Map
assert object.myList instanceof List
assert object.myList == [4, 8, 15, 16, 23, 42]
2. JsonOutput
JsonOutput
負責將 Groovy 物件序列化為 JSON 字串。它可以視為 JsonSlurper 的伴隨物件,是一個 JSON 剖析器。
JsonOutput
附帶重載的靜態 toJson
方法。每個 toJson
實作都採用不同的參數類型。靜態方法可以直接使用,或透過靜態匯入陳述式匯入方法。
toJson
呼叫的結果是包含 JSON 程式碼的 String
。
def json = JsonOutput.toJson([name: 'John Doe', age: 42])
assert json == '{"name":"John Doe","age":42}'
JsonOutput
不僅支援將原始資料、對應或清單資料類型序列化為 JSON,它更進一步,甚至支援序列化 POGO,也就是純粹的 Groovy 物件。
class Person { String name }
def json = JsonOutput.toJson([ new Person(name: 'John'), new Person(name: 'Max') ])
assert json == '[{"name":"John"},{"name":"Max"}]'
2.1. 自訂輸出
如果您需要控制序列化的輸出,可以使用 JsonGenerator
。JsonGenerator.Options
建構器可 used to create a customized generator. One or more options can be set on this builder in order to alter the resulting output. When you are done setting the options simply call the build()
method in order to get a fully configured instance that will generate output based on the options selected.
class Person {
String name
String title
int age
String password
Date dob
URL favoriteUrl
}
Person person = new Person(name: 'John', title: null, age: 21, password: 'secret',
dob: Date.parse('yyyy-MM-dd', '1984-12-15'),
favoriteUrl: new URL('https://groovy.dev.org.tw/'))
def generator = new JsonGenerator.Options()
.excludeNulls()
.dateFormat('yyyy@MM')
.excludeFieldsByName('age', 'password')
.excludeFieldsByType(URL)
.build()
assert generator.toJson(person) == '{"name":"John","dob":"1984@12"}'
可以使用閉包來轉換類型。這些閉包轉換器會註冊給定的類型,並在遇到該類型或其子類型時呼叫。閉包的第一個參數是與註冊轉換器的類型相符的物件,且此參數是必要的。閉包可以採用一個選擇性的第二個 String
參數,如果有的話,這個參數會設定為金鑰名稱。
class Person {
String name
URL favoriteUrl
}
Person person = new Person(name: 'John', favoriteUrl: new URL('https://groovy.dev.org.tw/json.html#_jsonoutput'))
def generator = new JsonGenerator.Options()
.addConverter(URL) { URL u, String key ->
if (key == 'favoriteUrl') {
u.getHost()
} else {
u
}
}
.build()
assert generator.toJson(person) == '{"name":"John","favoriteUrl":"groovy-lang.org"}'
// No key available when generating a JSON Array
def list = [new URL('https://groovy.dev.org.tw/json.html#_jsonoutput')]
assert generator.toJson(list) == '["https://groovy.dev.org.tw/json.html#_jsonoutput"]'
// First parameter to the converter must match the type for which it is registered
shouldFail(IllegalArgumentException) {
new JsonGenerator.Options()
.addConverter(Date) { Calendar cal -> }
}
2.1.1. 格式化輸出
如前例所示,JSON 輸出預設並非漂亮列印。不過,JsonOutput
中的 prettyPrint
方法可以解決這個問題。
def json = JsonOutput.toJson([name: 'John Doe', age: 42])
assert json == '{"name":"John Doe","age":42}'
assert JsonOutput.prettyPrint(json) == '''\
{
"name": "John Doe",
"age": 42
}'''.stripIndent()
prettyPrint
採用 String
作為單一參數;因此,它可以套用在任意的 JSON String
實例上,不只 JsonOutput.toJson
的結果。
2.2. 建構器
從 Groovy 建立 JSON 的另一種方法是使用 JsonBuilder
或 StreamingJsonBuilder
。這兩個建構器提供一個 DSL,允許制定一個物件圖,然後轉換為 JSON。
有關建構器的更多詳細資訊,請參閱涵蓋 JsonBuilder 和 StreamingJsonBuilder 的建構器章節。 |