解析和生成 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 标准支持以下原始数据类型:string、number、object、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 是一种强大的表达式语言,受多种不同数据格式的 slurper 支持(XmlSlurper 用于 XML 就是一个示例)。

有关更多详细信息,请参阅有关 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 中的值为 null 时,JsonSlurper 会将其补充为 Groovy 的 null 值。这与其他 JSON 解析器不同,后者使用库提供的单例对象表示 null 值。

1.1. 解析器变体

JsonSlurper 附带了几个解析器实现。每个解析器都适合不同的需求,对于某些场景,JsonSlurper 默认解析器可能不是所有情况下的最佳选择。以下是随附解析器实现的概述

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

  • JsonFastParserJsonParserCharArray 的一个特殊变体,是最快的解析器。然而,它并非默认解析器是有原因的。JsonFastParser 是一种所谓的索引覆盖解析器。在解析给定的 JSON String 期间,它会尽可能避免创建新的字符数组或 String 实例。它只保留指向底层原始字符数组的指针。此外,它会尽可能晚地延迟对象创建。如果将解析的映射放入长期缓存中,则必须小心,因为映射对象可能尚未创建,并且仍然仅由指向原始字符缓冲区的指针组成。然而,JsonFastParser 带有一种特殊的切片模式,它会尽早切割字符缓冲区,以保留原始缓冲区的一个小副本。建议将 JsonFastParser 用于 2MB 以下的 JSON 缓冲区,并牢记长期缓存限制。

  • 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