解析和生成 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 是一种强大的表达式语言,它受多种针对不同数据格式的解析器(例如,针对 XML 的 XmlSlurper
)支持。
有关更多详细信息,请参阅关于 GPath 表达式 的部分。 |
下表概述了 JSON 类型和相应的 Groovy 数据类型
JSON | Groovy |
---|---|
string |
|
number |
|
object |
|
array |
|
true |
|
false |
|
null |
|
date |
基于 |
无论何时 JSON 中的值为 null ,JsonSlurper 都会用 Groovy 的 null 值对其进行补充。这与其他将 null 值表示为库提供的单例对象的 JSON 解析器不同。 |
1.1. 解析器变体
JsonSlurper
带有几个解析器实现。每个解析器都适合不同的需求,可能在某些情况下,JsonSlurper
默认解析器并非所有情况下的最佳选择。以下是提供的解析器实现的概述
-
JsonParserCharArray
解析器基本上获取 JSON 字符串并在底层字符数组上进行操作。在值转换期间,它会复制字符子数组(一种称为“切片”的机制),并在其上进行操作。 -
JsonFastParser
是JsonParserCharArray
的一个特殊变体,也是最快的解析器。但是,它并非默认解析器是有原因的。JsonFastParser
是一个所谓的索引重叠解析器。在解析给定 JSONString
期间,它会尽可能地避免创建新的 char 数组或String
实例。它只保留对底层原始字符数组的指针。此外,它尽可能地延迟对象创建。如果解析的映射被放入长期缓存中,则必须注意,映射对象可能尚未创建,并且仍然只包含指向原始 char 缓冲区的指针。但是,JsonFastParser
具有一个特殊的切片模式,可以提前将 char 缓冲区切成块,以保留原始缓冲区的一个小副本。建议对小于 2MB 的 JSON 缓冲区使用JsonFastParser
,并牢记长期缓存限制。 -
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
生成器可以用于创建一个自定义生成器。可以在此生成器上设置一个或多个选项,以更改生成的输出。完成选项设置后,只需调用 build()
方法即可获得一个完全配置的实例,该实例将根据所选选项生成输出。
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-lang.cn/'))
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-lang.cn/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-lang.cn/json.html#_jsonoutput')]
assert generator.toJson(list) == '["https://groovy-lang.cn/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。 |