使用 Groovy 解析 JSON
作者:Paul King
发布日期:2022-07-10 02:00PM
Groovy 对处理各种结构化数据格式(如 JSON、TOML、YAML 等)有很好的支持。本博文着眼于 JSON.
在 Groovy 文档 中有关于此主题的相当好的文档。还有许多在线资源可以提供更多详细信息,包括 Groovy - JSON 教程、在 Groovy 中操作 JSON 和 Groovy 好处:放松……Groovy 将解析你复杂的 JSON,仅举几例。本博文提供了简要概述,并提供了有关各种选项的更多设置信息和详细信息。
内置体验
如果你已经安装了 Groovy 安装压缩包(或 Windows 上的 .msi),你将拥有 groovy-json
模块,其中包含 JsonSlurper
,因此这里和其他链接中展示的大部分示例应该可以开箱即用。
JsonSlurper
是解析 JSON 的主要类。
此示例展示了解析嵌入在字符串中的 JSON,但还有其他方法可以解析文件、URL 和其他流。
另一个使用 groovysh
的示例
paulk@pop-os:~$ groovysh
Groovy Shell (4.0.3, JVM: 18.0.1)
Type ':help' or ':h' for help.
----------------------------------------------------------------------------------------
groovy:000> new groovy.json.JsonSlurper().parseText('{ "myList": [1, 3, 5] }').myList
===> [1, 3, 5]
或使用 Jupyter/BeakerX 笔记本
Gradle
如果你使用的是像 Gradle 这样的构建工具,你可能更倾向于从依赖项存储库中引用你的依赖项,而不是拥有本地安装的发布版。
假设你在文件 src/test/groovy/JsonTest.groovy
中有以下使用 JsonSlurper
的测试
import groovy.json.JsonSlurper
import org.junit.Test
class JsonTest {
@Test
void testJson() {
def text = '{"person":{"name":"Guillaume","age":33,"pets":["dog","cat"]}}'
def json = new JsonSlurper().parseText(text)
assert json.person.pets.size() == 2
}
}
你可以在 build.gradle
文件中像下面这样引用相关的 Groovy 依赖项,在本例中是 groovy-json
和 groovy-test
apply plugin: 'groovy'
repositories {
mavenCentral()
}
dependencies {
testImplementation "org.apache.groovy:groovy-json:4.0.3" // for JsonSlurper
testImplementation "org.apache.groovy:groovy-test:4.0.3" // for tests
}
这两个工件都将核心 groovy
工件作为传递依赖项引入,因此无需显式引用它。
运行 gradle test
应该运行测试并生成报告
如果你愿意,也可以使用 groovy-all
工件,如下所示
apply plugin: 'groovy'
repositories {
mavenCentral()
}
dependencies {
testImplementation "org.apache.groovy:groovy-all:4.0.3"
}
该工件不包含任何 jar,但将所有常见的 Groovy 模块作为传递依赖项包含进来。
[注意:在早期的 Groovy 4 版本中,你可能需要将 Groovy 引用为平台,例如:testImplementation platform("org.apache.groovy:groovy-all:4.0.1")
。现在,只有在使用 groovy-bom
工件时才需要这样做。]
Maven
使用 Maven 构建工具时,你应该像下面这样创建一个 pom.xml
并使用两个插件
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>myGroupId</groupId>
<artifactId>groovy-json-maven</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.gmavenplus</groupId>
<artifactId>gmavenplus-plugin</artifactId>
<version>1.13.0</version>
<executions>
<execution>
<goals>
<goal>compileTests</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M7</version>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.apache.groovy</groupId>
<artifactId>groovy-json</artifactId>
<version>4.0.3</version>
</dependency>
<dependency>
<groupId>org.apache.groovy</groupId>
<artifactId>groovy-test</artifactId>
<version>4.0.3</version>
</dependency>
</dependencies>
</project>
或者,你可以再次像下面这个备用构建文件那样引用 groovy-all
工件
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>myGroupId</groupId>
<artifactId>groovy-json-maven</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.gmavenplus</groupId>
<artifactId>gmavenplus-plugin</artifactId>
<version>1.13.0</version>
<executions>
<execution>
<goals>
<goal>compileTests</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M7</version>
<dependencies>
<dependency>
<groupId>org.apache.maven.surefire</groupId>
<artifactId>surefire-junit47</artifactId>
<version>3.0.0-M7</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.apache.groovy</groupId>
<artifactId>groovy-all</artifactId>
<type>pom</type>
<version>4.0.3</version>
</dependency>
</dependencies>
</project>
引用 groovy-all
工件时,我们使用 <type>pom</type>
指定它是一个 pom 工件。我们还需要配置 surefire 插件以使用 JUnit4。groovy-all
工件还引入了 JUnit5 支持,surefire 插件会默认使用它,而找不到我们的测试。
运行测试应该得到以下结果
[INFO] ------------------------------------------------------ [INFO] T E S T S [INFO] ------------------------------------------------------ [INFO] Running JsonTest [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.36 s - in JsonTest
高级功能
为了适应不同的场景,JsonSlurper
由几个内部实现类提供支持。你不会直接访问这些类,而是在实例化 slurper 时设置解析器类型。
类型 | 何时使用 |
---|---|
CHAR_BUFFER |
默认情况下,最少意外的解析器,带有对整数、日期等的积极解析。 |
INDEX_OVERLAY |
用于 REST 调用、WebSocket 消息、AJAX、进程间通信。最快的解析器,使用索引指向某些现有字符缓冲区。 |
CHARACTER_SOURCE |
用于处理更大的 JSON 文件。 |
LAX |
允许注释,并且在许多情况下不允许使用引号或单引号。 |
以下是一个示例
import groovy.json.JsonSlurper
import static groovy.json.JsonParserType.*
def slurper = new JsonSlurper(type: LAX)
def json = slurper.parseText('''{person:{'name':"Guillaume","age":33,"pets":["dog" /* ,"cat" */]}}''')
assert json.person.pets == ['dog']
请注意 person 键缺少引号,name 键使用单引号,并且 "cat" 已被注释掉。严格的 JSON 解析器不允许这些更改。
其他 JSON 库
Groovy 不要求你使用 groovy-json
类。你可以使用你最喜欢的 Java 库与 Groovy 一起使用。你仍然可以从 Groovy 的许多简写符号中受益。
以下是用 Gson 的示例
@Grab('com.google.code.gson:gson:2.9.0')
import com.google.gson.JsonParser
def parser = new JsonParser()
def json = parser.parse('{"person":{"name":"Guillaume","age":33,"pets":["dog","cat"]}}')
assert json.person.pets*.asString == ['dog', 'cat']
以下是用 Jackson JSON 支持的示例
@Grab('com.fasterxml.jackson.core:jackson-databind:2.13.3')
import com.fasterxml.jackson.databind.ObjectMapper
def text = '{"person":{"name":"Guillaume","age":33,"pets":["dog","cat"]}}'
def json = new ObjectMapper().readTree(text)
assert json.person.pets*.asText() == ['dog', 'cat']
集成查询
Groovy 4 还支持语言集成查询语法,称为 GINQ 或 GQuery。我们也可以将它与 JSON 一起使用。
假设我们以 JSON 格式拥有关于水果、价格(每 100 克)和维生素 C 浓度(每 100 克)的信息
{
"prices": [
{"name": "Kakuda plum", "price": 13},
{"name": "Camu camu", "price": 25},
{"name": "Acerola cherries", "price": 39},
{"name": "Guava", "price": 2.5},
{"name": "Kiwifruit", "price": 0.4},
{"name": "Orange", "price": 0.4}
],
"vitC": [
{"name": "Kakuda plum", "conc": 5300},
{"name": "Camu camu", "conc": 2800},
{"name": "Acerola cherries", "conc": 1677},
{"name": "Guava", "conc": 228},
{"name": "Kiwifruit", "conc": 144},
{"name": "Orange", "conc": 53}
]
}
现在,假设我们预算有限,想要选择最具成本效益的水果来购买,帮助我们达到每日维生素 C 的需求量。我们将连接价格和vitC 信息,并按最具成本效益的水果排序。我们将选择前 2 名,以防我们去购物时第一选择缺货。我们的 GQuery 处理如下所示
def jsonFile = new File('fruit.json')
def json = new JsonSlurper().parse(jsonFile)
assert GQL {
from p in json.prices
join c in json.vitC on c.name == p.name
orderby c.conc / p.price in desc
limit 2
select p.name
} == ['Kakuda plum', 'Kiwifruit']
我们可以看到,对于这些数据,卡卡杜李子其次是奇异果是我们最好的选择。
快速性能比较
作为一个非常粗略的性能指标,JsonSlurper
使用了所有 4 种解析器类型以及 Gson 和 Jackson,用于解析来自 https://github.com/flowcommerce/json-reference 的时区值,并检查布里斯班的当前时区是否与悉尼的时区相同。此 json 文件绝非巨大。它只有不到 3000 行,大小不到 60K。在 4 次运行后,花费的最短时间(包括编译时间)被记录下来 - 这绝对是一个微基准测试,不应过分认真对待,但可能是一个粗略的指南。为了好玩,使用 GraalVM 制作了使用 INDEX_OVERLAY 类型设置的 Groovy JsonSlurper 脚本的原生版本。它的计时也包含在内。
$ time groovy GroovyJsonIndexOverlay.groovy
real 0m1.365s
user 0m4.157s
sys 0m0.145s
$ time groovy GroovyJsonCharacterSource.groovy
real 0m1.447s
user 0m4.472s
sys 0m0.174s
$ time groovy GroovyJsonLax.groovy
real 0m1.452s
user 0m4.338s
sys 0m0.171s
$ time groovy GroovyJson.groovy
real 0m1.383s
user 0m4.050s
sys 0m0.165s
$ time groovy Gson.groovy
real 0m1.814s
user 0m5.543s
sys 0m0.209s
$ time groovy Jackson.groovy
real 0m2.007s
user 0m6.332s
sys 0m0.208s
$ time ./groovyjsonindexoverlay
real 0m0.015s
user 0m0.011s
sys 0m0.004s
总结
我们已经看到了使用 Groovy 解析 JSON 设置项目的入门知识,以及根据场景可使用的众多选项中的一些。我们还看到了如何使用其他 JSON 库,在处理过程中使用 GQuery 语法,并查看了一些非常粗略的性能数据。