Write JSON or XML with Apache Groovy
Understanding how to work with JSON and XML is crucial for data exchange in modern applications. This article explores the various methods Groovy offers for writing both JSON and XML data.
The last installment of the series explored how to read them, this time we’re going to dive into the facilities Groovy provides for writing JSON and XML. (If you haven’t installed Groovy yet, please read the intro to this series.)
Starting with JSON, there are four approaches to writing:
- Brute force
- The
JsonOutputclass, which is notionally the opposite of theJsonSlurperseen in the previous article – it takes a GroovyMapand creates a JSON string - The
JsonGeneratorclass to customize the result of converting a GroovyMapto a JSON string; - The
JsonBuilderclass, which provides a declarative syntax for creating a JSON string.
I’ll skip the brute force approach, it’s just using a bunch of println() calls with Groovy GString instances to create the JSON string.
The JsonOutput solution is practical when we have a Groovy Map instance that we want to convert to JSON. For example, if we have some code that queries a database and puts the query results into a Map instance, then we can return that as JSON easily. Here’s a program that demonstrates the idea, using some recent Canadian house prices:
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)
Pretty straightforward. In lines one-15, we declare the Map instance we want to render as JSON; then in line 16, we use JsonOutput.toJson() to render it.
Run this as follows:
$ 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}}}
$
If we need a version with a nice readable layout, then wrap the call to toJson() with a JsonOutput.prettyPrint() call.
We aren’t limited to using a Map instance as an argument to toJson(). We can pass it class instances as well, whether instances of a wrapped primitive type like Integer, or something more complicated like a class we use to accumulate results.
The JsonGenerator solution gives us a way to control certain interesting aspects of the output. We use this ability by chaining together several calls that allow us to change date formats, locales, exclude fields by name or type or exclude nulls. Once those rules are built, we use the toJson() method on the generator instance. Mr. Haki has some nice examples.
The JsonBuilder class stands out as the most convenient approach (in my opinion) for writing JSON in Groovy. It leverages the concept of builders, a common pattern in Groovy, to provide a clear and concise syntax for constructing JSON output.
Groovy builders in general and JsonBuilder in particular are cool for two important reasons:
- They’re declarative, which in many cases is a clear and concise way to create something complicated;
- They’re not simply a static declaration, but allow dynamic behavior to be interwoven with the declarative structuring.
Let’s look at a simple example:
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())
Line three declares the JsonBuilder instance.
Lines four-12 use the builder to create the JSON output.
Line four declares the top-level JSON object, “users”;
Lines five-11 loop over the /etc/passwd file, line by line;
Line six splits the line into fields;
Lines seven-10 declare each second-level JSON object, whose field tag is the userid and whose components are the GCOS and home directory fields from /etc/passwd
Line 13 pretty-prints the JSON output.
We run this as follows to get:
$ 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 and Groovy
Moving to XML, the declarative nature of the builder DSL along with the ability to dynamically fetch the data facilitates creating XML (or HTML) from input data.
Let’s build an HTML fragment of the user info in the /etc/passwd file using groovy.xml.MarkupBuilder:
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 }
In line two we define a cell style.
In lines three-27 we use MarkupBuilder to generate an HTML <div> that contains a table of user information.
Line four creates the <table> element, with styling;
Lines five-13 create the row (<tr>) of table column headings (<th>);
Lines 14-25 loop over the lines in /etc/passwd, splitting each line into fields, and then putting those field values in <td> elements.
I often use this sort of technique to generate HTML reports from Groovy scripts that I run from the command line. I put the output from the script into a file, open that file in my browser, select all, and copy and paste to an email compose window. I would define the table and cell styles in a <style> element beforehand but I’ve found that most browser-based mailers don’t like that.
Run it as follows:
$ groovy Groovy22c.groovy > foo.html
The HTML looks like this:
<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>
And renders like this:
| user | passwd | uid | gid | gcos | home | 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 |
Conclusion
If the previous article was really more about slurpers and GPath than reading JSON and XML, this article is more about builders than writing JSON and XML.
Builders are wonderful because they are declarative and they invite dynamic behavior to be embedded within them. Worth mentioning is that declarative statements like td style:cellStyle, fields[0]
are method calls – td() is the method, and the rest of the line are the arguments. We could have written this as td(style:cellStyle, fields[0])
to make their true nature more obvious, but the parentheses don’t add anything to the clarity once you remember that they’re optional in most circumstances in Groovy.
You might also be wondering, “Who declared all those td() and other methods so that I could use them”? And the answer is, no one did. They are handled by Groovy’s Meta Object Programming capability. I didn’t cover this very groovy capability in this series, but I intend to write a future article (or series) about it, so please stay tuned!
