解析和生成 JSON

Groovy 集成了将 Groovy 对象与 JSON 之间进行转换的支持。专门用于 JSON 序列化和解析的类位于 groovy.json 包中。

1. JsonSlurper

JsonSlurper 是一个类,它将 JSON 文本或读取器内容解析为 Groovy 数据结构(对象),例如映射、列表和原始类型,如 IntegerDoubleBooleanString

该类带有一组重载的 parse 方法,以及一些特殊方法,例如 parseTextparseFile 等。在下一个示例中,我们将使用 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 标准支持以下原始数据类型:字符串、数字、对象、truefalsenullJsonSlurper 将这些 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

java.lang.String

number

java.lang.BigDecimaljava.lang.Integer

object

java.util.LinkedHashMap

array

java.util.ArrayList

true

true

false

false

null

null

date

基于 yyyy-MM-dd’T’HH:mm:ssZ 日期格式的 java.util.Date

无论何时 JSON 中的值为 nullJsonSlurper 都会用 Groovy 的 null 值对其进行补充。这与其他将 null 值表示为库提供的单例对象的 JSON 解析器不同。

1.1. 解析器变体

JsonSlurper 带有几个解析器实现。每个解析器都适合不同的需求,可能在某些情况下,JsonSlurper 默认解析器并非所有情况下的最佳选择。以下是提供的解析器实现的概述

  • JsonParserCharArray 解析器基本上获取 JSON 字符串并在底层字符数组上进行操作。在值转换期间,它会复制字符子数组(一种称为“切片”的机制),并在其上进行操作。

  • JsonFastParserJsonParserCharArray 的一个特殊变体,也是最快的解析器。但是,它并非默认解析器是有原因的。JsonFastParser 是一个所谓的索引重叠解析器。在解析给定 JSON String 期间,它会尽可能地避免创建新的 char 数组或 String 实例。它只保留对底层原始字符数组的指针。此外,它尽可能地延迟对象创建。如果解析的映射被放入长期缓存中,则必须注意,映射对象可能尚未创建,并且仍然只包含指向原始 char 缓冲区的指针。但是,JsonFastParser 具有一个特殊的切片模式,可以提前将 char 缓冲区切成块,以保留原始缓冲区的一个小副本。建议对小于 2MB 的 JSON 缓冲区使用 JsonFastParser,并牢记长期缓存限制。

  • JsonParserLaxJsonParserCharArray 解析器的特殊变体。它的性能特性与 JsonFastParser 类似,但不同之处在于它并不完全依赖于 ECMA-404 JSON 语法。例如,它允许使用注释、无引号字符串等。

  • JsonParserUsingCharacterSource 是一个专门用于处理非常大的文件的解析器。它使用一种称为“字符窗口”的技术来解析大型 JSON 文件(大型指的是在这个例子中,大于 2MB 的文件),并且具有恒定的性能特性。

JsonSlurper 的默认解析器实现是 JsonParserCharArrayJsonParserType 枚举包含上面描述的解析器实现的常量

实现 常量

JsonParserCharArray

JsonParserType#CHAR_BUFFER

JsonFastParser

JsonParserType#INDEX_OVERLAY

JsonParserLax

JsonParserType#LAX

JsonParserUsingCharacterSource

JsonParserType#CHARACTER_SOURCE

更改解析器实现就像使用对 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. 自定义输出

如果您需要控制序列化输出,可以使用 JsonGeneratorJsonGenerator.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 的另一种方法是使用 JsonBuilderStreamingJsonBuilder。这两个生成器都提供了一个 DSL,允许构建一个对象图,然后将其转换为 JSON。

有关生成器的更多详细信息,请参阅生成器章节,其中涵盖了 JsonBuilderStreamingJsonBuilder