person writing on white paper

使用 Apache Groovy 编写 JSON 或 XML

从 Groovy 脚本生成格式良好的 HTML 报告。本指南探讨了简化电子邮件报告的技术,同时…
首页 » 博客 » 使用 Apache Groovy 编写 JSON 或 XML

了解如何使用 JSON 和 XML 对于现代应用程序中的数据交换至关重要。本文探讨了 Groovy 提供的用于编写 JSON 和 XML 数据的各种方法。

本系列的上一篇文章探讨了如何读取它们,这次我们将深入研究 Groovy 提供的用于编写 JSON 和 XML 的工具。(如果您尚未安装 Groovy,请 阅读本系列的介绍 系列。) 

从 JSON 开始,有四种编写方法

  1. 暴力破解
  2. JsonOutput 类,从概念上讲,它与上一篇文章中看到的 JsonSlurper 相反——它接受 Groovy Map 并创建一个 JSON 字符串
  3. JsonGenerator 类,用于自定义将 Groovy Map 转换为 JSON 字符串的结果;
  4. 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. 它们是声明性的,在许多情况下,这是一种清晰简洁的方式来创建复杂的东西;
  2. 它们不仅仅是静态声明,而且允许动态行为与声明性结构交织在一起。

让我们看一个简单的例子

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组 IDGCOS家目录Shell
rootx00root/root/bin/bash
daemonx11daemon/usr/sbin/usr/sbin/nologin
binx22bin/bin/usr/sbin/nologin

结论

如果上一篇文章更多是关于解析器GPath,而不是读取 JSON 和 XML,那么本文更多是关于构建器,而不是编写 JSON 和 XML。

构建器非常棒,因为它们是声明性的,并且它们允许将动态行为嵌入到其中。值得一提的是,像 td style:cellStyle, fields[0] 这样的声明性语句

是方法调用——td() 是方法,而行的其余部分是参数。我们可以将其写成 td(style:cellStyle, fields[0])

为了使它们的真实性质更加明显,但是一旦您记住括号在 Groovy 中的大多数情况下是可选的,那么括号就不会增加任何清晰度。

您可能还会想,“是谁声明了所有这些 td() 和其他方法,以便我可以使用它们”?答案是没有人。它们由Groovy 的元对象编程功能处理。我没有在本系列中介绍这个非常 groovy 的功能,但我打算在未来的文章(或系列)中介绍它,所以请继续关注!

作者

留下评论

留下回复

如果您喜欢这篇文章,您可能也喜欢这些