Groovy Goodness: Customizing JSON Output
Groovy 2.5.0 adds the possibility to customize JSON output via a JsonGenerator
instance.
The easiest way to turn an object into a JSON string value is via JsonOutput.toJson
.
This method uses a default JsonGenerator
with sensible defaults for JSON output.
But we can customize this generator and create JSON output using the custom generator.
To create a custom generator we use a builder accessible via JsonGenerator.Options
.
Via a fluent API we can for example ignore fields with null
values in the output, change the date format for dates and ignore fields by their name or type of the value.
And we can add a custom converter for types via either an implementation of the conversion as Closure
or implementation of the JsonGenerator.Converter
interface.
To get the JSON string we simple invoke the toJson
method of our generator.
In the following example Groovy code we have a Map
with data and we want to convert it to JSON.
First we use the default generator and then we create our own to customize the JSON output:
// Sample class to be used in JSON.
@groovy.transform.TupleConstructor
class Student {
String firstName, lastName
}
def data =
[student: new Student('Hubert', 'Klein Ikkink'),
dateOfBirth: Date.parse('yyyyMMdd', '19730709'),
website: 'https://www.mrhaki.com'.toURL(),
password: 'IamSecret',
awake: Optional.empty(),
married: Optional.of(true),
location: null,
currency: '\u20AC' /* Unicode EURO */]
import groovy.json.JsonGenerator
import groovy.json.JsonGenerator.Converter
// Default JSON generator. This generator is used by
// Groovy to create JSON if we don't specify our own.
// For this example we define the default generator
// explicitly to see the default output.
def jsonDefaultOutput = new JsonGenerator.Options().build()
// Use generator to create JSON string.
def jsonDefaultResult = jsonDefaultOutput.toJson(data) // Or use JsonOutput.toJson(data)
assert jsonDefaultResult == '{"student":{"firstName":"Hubert","lastName":"Klein Ikkink"},' +
'"dateOfBirth":"1973-07-08T23:00:00+0000","website":"https://www.mrhaki.com","password":"IamSecret",' +
'"awake":{"present":false},"married":{"present":true},"location":null,"currency":"\\u20ac"}'
// Define custom rules for JSON that will be generated.
def jsonOutput =
new JsonGenerator.Options()
.excludeNulls() // Do not include fields with value null.
.dateFormat('EEEE dd-MM-yyyy', new Locale('nl', 'NL')) // Set format for dates.
.timezone('Europe/Amsterdam') // Set timezone to be used for formatting dates.
.excludeFieldsByName('password') // Exclude fields with given name(s).
.excludeFieldsByType(URL) // Exclude fields of given type(s).
.disableUnicodeEscaping() // Do not escape UNICODE.
.addConverter(Optional) { value -> value.orElse('UNKNOWN') } // Custom converter for given type defined as Closure.
.addConverter(new Converter() { // Custom converter implemented via Converter interface.
/**
* Indicate which type this converter can handle.
*/
boolean handles(Class type) {
return Student.isAssignableFrom(type)
}
/**
* Logic to convert Student object.
*/
Object convert(Object student, String key) {
"$student.firstName $student.lastName"
}
})
.build() // Create the converter instance.
// Use generator to create JSON from Map data structure.
def jsonResult = jsonOutput.toJson(data)
assert jsonResult == '{"student":"Hubert Klein Ikkink",' +
'"dateOfBirth":"maandag 09-07-1973",' +
'"awake":"UNKNOWN","married":true,"currency":"€"}'
The JsonBuilder
and StreamingJsonBuilder
classes now also support the use of a JsonGenerator
instance.
The generator is used when the JSON output needs to be created.
The internal data structure of the builder is not altered by using a custom generator.
In the following example we use the custom generator of the previous example and apply it with a JsonBuilder
and StreamingJsonBuilder
instance:
import groovy.json.JsonBuilder
// We can use a generator instance as constructor argument
// for JsonBuilder.
The generator is used when we create the
// JSON string. It will not effecct the internal JSON data structure.
def jsonBuilder = new JsonBuilder(jsonOutput)
jsonBuilder {
student new Student('Hubert', 'Klein Ikkink')
dateOfBirth Date.parse('yyyyMMdd', '19730709')
website 'https://www.mrhaki.com'.toURL()
password 'IamSecret'
awake Optional.empty()
married Optional.of(true)
location null
currency '\\u20AC'
}
def jsonBuilderResult = jsonBuilder.toString()
assert jsonBuilderResult == '{"student":"Hubert Klein Ikkink",' +
'"dateOfBirth":"maandag 09-07-1973",' +
'"awake":"UNKNOWN","married":true,"currency":"€"}'
// The internal structure is unaffected by the generator.
assert jsonBuilder.content.password == 'IamSecret'
assert jsonBuilder.content.website.host == 'www.mrhaki.com'
import groovy.json.StreamingJsonBuilder
new StringWriter().withWriter { output ->
// As with JsonBuilder we can provide a custom generator via
// the constructor for StreamingJsonBuilder.
def jsonStreamingBuilder = new StreamingJsonBuilder(output, jsonOutput)
jsonStreamingBuilder {
student new Student('Hubert', 'Klein Ikkink')
dateOfBirth Date.parse('yyyyMMdd', '19730709')
website 'https://www.mrhaki.com'.toURL()
password 'IamSecret'
awake Optional.empty()
married Optional.of(true)
location null
currency '\\u20AC'
}
def jsonStreamingBuilderResult = output.toString()
assert jsonStreamingBuilderResult == '{"student":"Hubert Klein Ikkink",' +
'"dateOfBirth":"maandag 09-07-1973",' +
'"awake":"UNKNOWN","married":true,"currency":"€"}'
}
Written with Groovy 2.5.0.