Skip to main content
Ben Nadel at cf.Objective() 2010 (Minneapolis, MN) with: Mike Canonigo
Ben Nadel at cf.Objective() 2010 (Minneapolis, MN) with: Mike Canonigo

Pretty-Printing JSON Using GSON In Lucee CFML 5.3.9.141

By
Published in Comments (7)

I'm currently working on a proof-of-concept (POC) for a feature flag system in Lucee CFML. And, for my POC data persistence layer, I've been using a simple, flat JSON (JavaScript Object Notation) text file. This works great; but, I found myself wishing that the JSON file was pretty-printed so that I could more easily debug my data persistence algorithms. ColdFusion's native serializeJson() function doesn't have a pretty-print feature; but, I was able to use Google's GSON library to generate pretty-printed JSON output in Lucee CFML 5.3.9.141.

One of the most exciting features of Lucee CFML is the fact that we can dynamically load JAR files with the createObject() function. Which means, in order to get GSON to work in ColdFusion, all I had to do was download the latest copy from Maven, and then provide the one JAR file in my createObject() call:

<cfscript>

	/**
	* I build the configured GSON object.
	*/
	public any function buildGson() {

		// JAR files downloaded form Maven Repository.
		var jarPaths = [
			// https://mvnrepository.com/artifact/com.google.code.gson/gson/2.9.0
			expandPath( "./vendor/gson-2.9.0/gson-2.9.0.jar")
		];

		// Normally, JSON is output as a single line, which is what makes other formats
		// like NDJSON (Newline-Delimited JSON) possible. But, for debugging purposes, it
		// is sometimes nice to see JSON rendered in a multi-line, human-friendly format.
		// That's what .setPrettyPrinting() is doing here - turning on the human-friendly
		// output formatting during the serialization process.
		var gson = createObject( "java", "com.google.gson.GsonBuilder", jarPaths )
			.init()
			.setPrettyPrinting()
			.create()
		;

		return( gson );

	}

</cfscript>

By default, the GSON client generates a single line of JSON. But, by using the "builder" to construct the GSON client, we can change its behavior. In this case, I'm calling the .setPrettyPrinting() builder method to turn-on the pretty-printing functionality. This will enable pretty-printing for all future calls to the resultant GSON client - from what I can see, there's no way to turn this feature on-or-off on per-serialization basis.

And, now that we have our GSON client, let's try to serialize some ColdFusion data structures and examine our output:

<cfscript>

	// Build our sample data to run through the GSON serialization process.
	data = [
		name: "CAFFEINE",
		vendor: "ProLab",
		tagline: "Performance Simplified",
		count: 100,
		price: 6.79,
		strength: 200,
		activeIngredients: [
			"Caffeine",
			"Calcium"
		],
		inactiveIngredients: [
			"Stearic Acid",
			"Cellulose Gum",
			"Silica",
			"Magnesium Stearate",
			"Methylcellulose",
			"Glycerin"
		],
		allergens: [
			Yeast: false,
			Wheat: false,
			Corn: false,
			Milk: false,
			Egg: false,
			Soy: false
		],
		// CAUTION: Date objects will not serialize with the default configuration. I
		// believe that a customer serializer would need to be implemented. Personally, I
		// like to store dates as UTC-milliseconds if I'm storing them in a flat format.
		expiresAt: now().add( "y", 10 )
	];

	serializedData = buildGson()
		.toJson( data )
	;

	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //

	// Output the JSON using PRE so that we keep all the embedded white space provided
	// by the pretty-print configuration..
	echo( "<pre>#encodeForHtml( serializedData )#</pre>" );

	// Let's also compare the original (source) data representation to the deserialized
	// representation from our output.
	```
	<div style="display: flex ; gap: 20px ;">
		<div>
			<cfdump
				label="Source Data (Original)"
				var="#data#"
			/>
		</div>
		<div>
			<cfdump
				label="Deserialized Data (From GSON String)"
				var="#deserializeJson( serializedData )#"
			/>
		</div>
	</div>
	```

	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //

	/**
	* I build the configured GSON object.
	*/
	public any function buildGson() {

		// JAR files downloaded form Maven Repository.
		var jarPaths = [
			// https://mvnrepository.com/artifact/com.google.code.gson/gson/2.9.0
			expandPath( "./vendor/gson-2.9.0/gson-2.9.0.jar")
		];

		// Normally, JSON is output as a single line, which is what makes other formats
		// like NDJSON (Newline-Delimited JSON) possible. But, for debugging purposes, it
		// is sometimes nice to see JSON rendered in a multi-line, human-friendly format.
		// That's what .setPrettyPrinting() is doing here - turning on the human-friendly
		// output formatting during the serialization process.
		var gson = createObject( "java", "com.google.gson.GsonBuilder", jarPaths )
			.init()
			.setPrettyPrinting()
			.create()
		;

		return( gson );

	}

</cfscript>

Here, I'm pretty-printing the JSON to the browser using a <pre> tag so that we get to see all of the white space that the GSON client embedded in the serialized data. I'm also running the result back through ColdFusion's deserializeJson() just for the sake of completeness. And, when we run this ColdFusion code, we get the following output:

Pretty-printed JSON output using GSON in Lucee CFML.

And, we can see the source data and deserialized data side-by-side:

The one obvious issue with using the GSON serializer in my demo is that we lose the date/time stamp, expiresAt. That said, I don't normally serialize and store raw date values. Instead, if I needed to store them in JSON, I would call the .getTime() method on the Date object and store the underlying UTC-milliseconds. Then, during deserialization, I could construct a ColdFusion date from the UTC-milliseconds.

That said, I would likely only use GSON in POC and demo applications. As such, this limitation isn't really a concern for me.

The native serializeJson() method is what I use in my production ColdFusion applications. But, when persisting data to a JSON file for a demo or a proof-of-concept, the GSON client seems like an easy option to enable human-readable data persistence in Lucee CFML.

Want to use code from this post? Check out the license.

Reader Comments

41 Comments

Thanks Ben. I recently looked at GSON in my exploration of Serialisation options for a Lucee project (mainly trying to get lowercase keys). Forum post

One thing I noticed (probably not relevant for your use-case here) is that GSON is not accurate in its JSON representation. For example, your count and strength integer properties end up with decimal places. This could be an issue for people thinking to use GSON for an API.

I also had some issues with strings that had character sequences that GSON "helpfully" escaped within the string!

Try this structure (you might recognise it! 😉 ) and see the results esp "Password":

tricia = {
	FIRSTNAME = "Tricia",
	LASTNAME = "Smith",
	DATEOFBIRTH = dateConvert( "local2utc", "1975/01/01" ),
	NICKNAME = "Trish",
	FAVORITECOLOR = "333333",
	FAVORITENUMBERS = [ true, 4.0, 137, false ],
	AGE = 38,
	CREATEDAT = now(),
	PASSWORD = "I<3ColdFusion&Cookies"
};

GSON has some good features and seems pretty fast, but accuracy might be an issue depending on use-case.

Murray

15,848 Comments

@Murray,

Yeah, I tried looking at the GSON "builder" to see if I could define some sort of setting for numbers. They have some number strategy options, but it looks like they were for the deserialization aspects, not the serialization aspects. You can define some sort of custom Java classes; but, they have to implement other Java classes, which isn't something I know how to do in Lucee / ACF.

I'll take a look at the Forem post - thanks for the link.

41 Comments

@Ben,

Yes, the GSON builder with its customisable policies and strategies is a good pattern that makes the serialization very ... customisable. However, I also found it didn't play well with Lucee Structs without having to do even more work.

I thought the idea was a good one and made a Lucee language proposal to add an optional callback to the SerializeJSON() function passing each key:value pair so developers could customise both the key formatting and value validation, conversion and formatting if they needed to - a bit like your JsonSerializer.cfc, but by a callback rather than an options map.

Unfortunately the idea was rejected. Oh well.

Anyway... we digress from your original post about pretty printing!

Thanks,
Murray

15,848 Comments

@Murray,

That actually sounds a lot like how the JSON.stringify() method works in JavaScript. It's signature is like this:

JSON.stringify( value [, replacer [, space ]] )

... where the second param is your callback, and the third param is for the pretty-printing functionality that I'm doing in GSON. So, clearly someone else thought it was a good idea 🙃

84 Comments

To prevent HTML escaping (and make your JSON potentially HTML unsafe), use disableHtmlEscaping.

The disableHtmlEscaping() method tells Gson not to escape HTML characters such as <, >, &, =, and '.

var gson = createObject("java", "com.google.gson.GsonBuilder", jarPaths)
	.init()
	.disableHtmlEscaping()
	.setPrettyPrinting()
	.create()
;
15,848 Comments

@James,

I'm a bit confused—wouldn't escaping the HTML normally make it more safe? Is there some sort of combination of escape sequences that can expose a vulnerability?

Post A Comment — I'd Love To Hear From You!

Post a Comment

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel