
使用 Apache Groovy 编写 JSON 或 XML
了解如何使用 JSON 和 XML 对于现代应用程序中的数据交换至关重要。本文探讨了 Groovy 提供的用于编写 JSON 和 XML 数据的各种方法。
本系列的上一篇文章探讨了如何读取它们,这次我们将深入研究 Groovy 提供的用于编写 JSON 和 XML 的工具。(如果您尚未安装 Groovy,请 阅读本系列的介绍 系列。)
从 JSON 开始,有四种编写方法
- 暴力破解
JsonOutput
类,从概念上讲,它与上一篇文章中看到的JsonSlurper
相反——它接受 GroovyMap
并创建一个 JSON 字符串JsonGenerator
类,用于自定义将 GroovyMap
转换为 JSON 字符串的结果;JsonBuilder
类,它提供了一种声明性语法来创建 JSON 字符串。
我将跳过暴力破解方法,它只是使用一堆带有 Groovy GString
实例的 println()
调用来创建 JSON 字符串。
当我们有一个想要转换为 JSON 的 Groovy Map
实例时,JsonOutput
解决方案是实用的。例如,如果我们有一些代码查询数据库并将查询结果放入 Map
实例中,那么我们可以轻松地将其作为 JSON 返回。这是一个演示该想法的程序,使用了最近的加拿大房价
1 import groovy.json.JsonOutput
2 def canadaHousePriceHistory = [
3 '2023-05': [
4 BC: [avgPrice: 1019145,pctMonthChange: 2.37, pctYearChange: 2.98],
5 ON: [avgPrice: 928897, pctMonthChange: 1.96, pctYearChange: -1.00],
6 QC: [avgPrice: 481737, pctMonthChange: 4.08, pctYearChange: -3.00],
7 AB: [avgPrice: 470037, pctMonthChange: 1.72, pctYearChange: 3.08],
8 NS: [avgPrice: 458175, pctMonthChange: 3.75, pctYearChange: 1.00],
9 PE: [avgPrice: 379924, pctMonthChange:-0.05, pctYearChange: -7.00],
10 MB: [avgPrice: 358391, pctMonthChange: 1.36, pctYearChange: -8.00],
11 SK: [avgPrice: 315444, pctMonthChange: 5.91, pctYearChange: 0.00],
12 NB: [avgPrice: 312351, pctMonthChange: 0.00, pctYearChange: 2.00],
13 NL: [avgPrice: 271953, pctMonthChange:-8.68, pctYearChange: -8.00]
14 ]
15 ]
16 println JsonOutput.toJson(canadaHousePriceHistory)
非常简单明了。在第 1-15 行中,我们声明了要渲染为 JSON 的 Map
实例;然后在第 16 行中,我们使用 JsonOutput.toJson()
来渲染它。
按如下方式运行
$ groovy Groovy22a.groovy
{"2023-05":{"BC":{"avgPrice":1019145,"pctMonthChange":2.37,"pctYearChange":2.98},"ON":{"avgPrice":928897,"pctMonthChange":1.96,"pctYearChange":-1.00},"QC":{"avgPrice":481737,"pctMonthChange":4.08,"pctYearChange":-3.00},"AB":{"avgPrice":470037,"pctMonthChange":1.72,"pctYearChange":3.08},"NS":{"avgPrice":458175,"pctMonthChange":3.75,"pctYearChange":1.00},"PE":{"avgPrice":379924,"pctMonthChange":-0.05,"pctYearChange":-7.00},"MB":{"avgPrice":358391,"pctMonthChange":1.36,"pctYearChange":-8.00},"SK":{"avgPrice":315444,"pctMonthChange":5.91,"pctYearChange":0.00},"NB":{"avgPrice":312351,"pctMonthChange":0.00,"pctYearChange":2.00},"NL":{"avgPrice":271953,"pctMonthChange":-8.68,"pctYearChange":-8.00}}}
$
如果我们需要一个具有良好可读布局的版本,则将对 toJson()
的调用包装在 JsonOutput.prettyPrint()
调用中。
我们不限于使用 Map 实例作为 toJson()
的参数。我们也可以将类实例传递给它,无论是像 Integer 这样的包装原始类型的实例,还是更复杂的实例,例如我们用来累积结果的类。
JsonGenerator
解决方案为我们提供了一种控制输出某些有趣方面的方法。我们通过链接几个调用来使用此功能,这些调用允许我们更改日期格式、语言环境、按名称或类型排除字段或排除 null 值。一旦构建了这些规则,我们就可以在生成器实例上使用 toJson()
方法。Haki 先生有一些很好的例子。
JsonBuilder 类脱颖而出,成为在 Groovy 中编写 JSON 最方便的方法(在我看来)。它利用了构建器的概念,这是 Groovy 中常见的模式,为构建 JSON 输出提供了清晰简洁的语法。
一般来说,Groovy 构建器,尤其是 JsonBuilder,之所以很酷,有两个重要原因
- 它们是声明性的,在许多情况下,这是一种清晰简洁的方式来创建复杂的东西;
- 它们不仅仅是静态声明,而且允许动态行为与声明性结构交织在一起。
让我们看一个简单的例子
1 import groovy.json.JsonBuilder
2 import groovy.json.JsonOutput
3 def jb = new JsonBuilder()
4 jb.users {
5 new File('/etc/passwd').eachLine { line ->
6 def fields = line.split(':')
7 "${fields[0]}" {
8 gcos fields[4]
9 home fields[5]
10 }
11 }
12 }
13 println JsonOutput.prettyPrint(jb.toString())
第三行声明了 JsonBuilder
实例。
第 4-12 行使用构建器创建 JSON 输出。
第四行声明了顶层 JSON 对象,“users”
;
第 5-11 行逐行循环遍历 /etc/passwd
文件;
第六行将该行拆分为字段;
第 7-10 行声明了每个二级 JSON 对象,其字段标签是 userid,其组件是来自 /etc/passwd
的 GCOS 和 home 目录字段
第 13 行漂亮地打印了 JSON 输出。
我们按如下方式运行以获得
$ groovy Groovy22b.groovy { “users”: { “root”: { “gcos”: “root”, “home”: “/root” }, “daemon”: { “gcos”: “daemon”, “home”: “/usr/sbin” }, “bin”: { “gcos”: “bin”, “home”: “/bin” }, … “fwupd-refresh”: { “gcos”: “fwupd-refresh user,,,”, “home”: “/run/systemd” }, “cups-browsed”: { “gcos”: “”, “home”: “/nonexistent” } } … }
XML 和 Groovy
转到 XML,构建器 DSL 的声明性性质以及动态获取数据的能力有助于从输入数据创建 XML(或 HTML)。
让我们使用 groovy.xml.MarkupBuilder
构建 /etc/passwd
文件中用户信息 HTML 片段
1 import groovy.xml.MarkupBuilder
2 def cellStyle = 'border: 1px solid #dddddd; border-collapse: collapse; padding: 8px; text-align: center;'
3 println new MarkupBuilder().div {
4 table style:'border: 1px solid #dddddd; border-collapse: collapse;', {
5 tr {
6 th style:cellStyle, 'user'
7 th style:cellStyle, 'passwd'
8 th style:cellStyle, 'uid'
9 th style:cellStyle, 'gid'
10 th style:cellStyle, 'gcos'
11 th style:cellStyle, 'home'
12 th style:cellStyle, 'shell'
13 }
14 new File('/etc/passwd').eachLine { line ->
15 def fields = line.split(':')
16 tr {
17 td style:cellStyle, fields[0]
18 td style:cellStyle, fields[1]
19 td style:cellStyle, fields[2]
20 td style:cellStyle, fields[3]
21 td style:cellStyle, fields[4]
22 td style:cellStyle, fields[5]
23 td style:cellStyle, fields[6]
24 }
25 }
26 }
27 }
在第二行中,我们定义了一个单元格样式。
在第 3-27 行中,我们使用 MarkupBuilder
生成一个 HTML <div>
,其中包含用户信息表。
第四行创建了具有样式的 <table>
元素;
第 5-13 行创建了表列标题 (<th>
) 的行 (<tr>
);
第 14-25 行循环遍历 /etc/passwd
中的行,将每行拆分为字段,然后将这些字段值放入 <td>
元素中。
我经常使用这种技术从我从命令行运行的 Groovy 脚本生成 HTML 报告。我将脚本的输出放入文件中,在浏览器中打开该文件,全选,然后复制并粘贴到电子邮件撰写窗口。我会在事先在 <style>
元素中定义表格和单元格样式,但我发现大多数基于浏览器的邮件程序都不喜欢这样做。
按如下方式运行
$ groovy Groovy22c.groovy > foo.html
HTML 看起来像这样
<div>
<table style='border: 1px solid #dddddd; border-collapse: collapse;'>
<tr>
<th style='border: 1px solid #dddddd; border-collapse: collapse; padding: 8px; text-align: center;'>user</th>
<th style='border: 1px solid #dddddd; border-collapse: collapse; padding: 8px; text-align: center;'>passwd</th>
<th style='border: 1px solid #dddddd; border-collapse: collapse; padding: 8px; text-align: center;'>uid</th>
<th style='border: 1px solid #dddddd; border-collapse: collapse; padding: 8px; text-align: center;'>gid</th>
<th style='border: 1px solid #dddddd; border-collapse: collapse; padding: 8px; text-align: center;'>gcos</th>
<th style='border: 1px solid #dddddd; border-collapse: collapse; padding: 8px; text-align: center;'>home</th>
<th style='border: 1px solid #dddddd; border-collapse: collapse; padding: 8px; text-align: center;'>shell</th>
…
<td style='border: 1px solid #dddddd; border-collapse: collapse; padding: 8px; text-align: center;'>/usr/sbin/nologin</td>
</tr>
</table>
</div>
渲染效果如下
用户 | 密码 | 用户 ID | 组 ID | GCOS | 家目录 | Shell |
---|---|---|---|---|---|---|
root | x | 0 | 0 | root | /root | /bin/bash |
daemon | x | 1 | 1 | daemon | /usr/sbin | /usr/sbin/nologin |
bin | x | 2 | 2 | bin | /bin | /usr/sbin/nologin |
结论
如果上一篇文章更多是关于解析器和 GPath
,而不是读取 JSON 和 XML,那么本文更多是关于构建器,而不是编写 JSON 和 XML。
构建器非常棒,因为它们是声明性的,并且它们允许将动态行为嵌入到其中。值得一提的是,像 td style:cellStyle, fields[0] 这样的声明性语句
是方法调用——td()
是方法,而行的其余部分是参数。我们可以将其写成 td(style:cellStyle, fields[0])
为了使它们的真实性质更加明显,但是一旦您记住括号在 Groovy 中的大多数情况下是可选的,那么括号就不会增加任何清晰度。
您可能还会想,“是谁声明了所有这些 td()
和其他方法,以便我可以使用它们”?答案是没有人。它们由Groovy 的元对象编程功能处理。我没有在本系列中介绍这个非常 groovy 的功能,但我打算在未来的文章(或系列)中介绍它,所以请继续关注!
留下评论