Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at dev.Objective() 2015 (Bloomington, MN) with: Ryan Vikander
Ben Nadel at dev.Objective() 2015 (Bloomington, MN) with: Ryan Vikander@rvikander )

Understanding Struct Key-Casing Using SerializeJson() In Lucee 5.3.2.77

By Ben Nadel on
Tags: ColdFusion

In general, Lucee CFML has really solid JSON (JavaScript Object Notation) support because it stores data using native Types under the hood. Because of this, it never suffered from the random Type conversions that plagued Adobe ColdFusion developers when using serializeJson() (ex, converting "Yes" to "true", and things of that nature). Maintaining key-casing of Struct Keys, however, is not super straightforward. There are a number of Struct key-casing settings that you can define; and, they each have different scope and level of precedence. This is my attempt (through trial and error) to codify a mental model for these settings.

If you spin-up a new instance of Lucee 5.3.2.77 using CommandBox, the default behavior for Struct serialization is that all quoted-keys will maintain their case and all unquoted-keys will be converted to upper case. As such, the default behavior for the following ColdFusion code:

public void function onRequest( required string scriptName ) {

	var data = {
		myKey: "myValue",
		"myQuotedKey": "myValue"
	};

	dump( serializeJson( data ) );

}

... gives us the serialized output using serializeJson():

{ "myQuotedKey": "myValue", "MYKEY": "myValue" }

Notice that the quoted-key maintained its case and the unquoted key was implicitly converted to uppercase.

To get a more "natural" JSON serialization behavior in Lucee CFML - one that plays nicest with JavaScript and AJAX (Asynchronous JavaScript and JSON) - go into the Lucee Server Admin and, within the Language/Compiler section, set the Key Case setting to be Preserve case:

Lucee CFML Server context setting for Key Case set to Preserve Case.

Ultimately, what this Lucee Server Admin setting does is update the lucee-server.xml configuration file. Specifically, it sets the compiler directive, dot-notation-upper-case to false:

<compiler
	dot-notation-upper-case="false"
/>

With the dot-notation-upper-case set to false, if we re-run the Lucee CFML code from above, we get the following output:

{ "myQuotedKey": "myValue", "myKey": "myValue" }

This time, both the quoted and unquoted keys maintained the expected casing based on the notation as defined within the code itself.

At this point, you can be done. Your Lucee CFML code will behave the way you want it to when calling serializeJson(). However, this is not the extent of the settings for Struct-key management. So, let's take a quick look at the other options.

First, you can use JVM System Properties to define the dot-notation-upper-case compiler setting outlined above. If you define the System Property (or, supposedly ENV variable) lucee.preserve.case=true, then you will get the natural JSON handling. With CommandBox, I am using the jvm options in my server.json file to test this:

{
    "app":{
        "cfengine": "lucee"
    },
    "jvm":{
        "args": "-Dlucee.preserve.case=true"
    }
}

Here, the -D prefix denotes a System Property configuration. But, there is one huge caveat: the lucee-server.xml configuration file has a higher precedence than the System Properties. Which means, if the dot-notation-upper-case compiler directive is defined in the lucee-server.xml file, your attempt to use the System Property lucee.preserve.case=true will be completely ignored. As such, the System Property approach is probably not going to help you very much.

The next option is to use the CFProcessingDirective tag with the preserveCase attribute set to true.

public void function onRequest( required string scriptName ) {

	// This will tell the Lucee Compiler to use more "natural" struct key-casing.
	processingDirective preserveCase = true;

	var data = {
		myKey: "myValue",
		"myQuotedKey": "myValue"
	};

	dump( serializeJson( data ) );

}

With the processingDirective tag in place, running the above Lucee CFML code will give us the following output:

{ "myQuotedKey": "myValue", "myKey": "myValue" }

Again, we can see that both the quoted and unquoted keys maintained the expected casing based on the notation as defined within the code itself.

Unfortunately, the processingDirective tag is scoped to the CFML Template. Which means, using this directive in the Application.cfc, for example, will have no impact on your other CFM and CFC files. It literally has to be included in every template that defines a Struct-key.

NOTE: The key-casing seems to be applied at key definition time, not at serialization time. As such, collocating the processingDirective with the serializeJson() call will do nothing if the Struct-in-question was defined in a separate CFML template.

The final configuration option for Struct serialization is the Application.cfc setting, preserveCaseForStructKey. This sub-serialization option is a bit confusing. When set to true (the default behavior), it only affects quoted keys, allowing them to maintain case during serialization. However, when this option is set to false, it affects both quoted and unquoted keys, converting them all to uppercase during serialization.

As such, if we run the following Lucee CFML code:

component
	output = false
	hint = "I provide the application's settings and event handlers."
 	{

	this.name = hash( getCurrentTemplatePath() );

	// By setting this value to FALSE, all Struct keys will be converted to // By setting this value to FALSE, all Struct keys will be converted to UPPERCASE
	// during serialization.
	// --
	// CAUTION: This FALSE value overrides the processingDirective tag and the "Preserve
	// Case" settings within the Lucee Server Admin.
	this.serialization.preserveCaseForStructKey = false; // (default: true)


	/**
	* I implement the request response handler (overriding the implicit handler).
	* 
	* @scriptName I am the template being requested.
	* @output false
	*/
	public void function onRequest( required string scriptName ) {

		var data = {
			myKey: "myValue",
			"myQuotedKey": "myValue"
		};

		dump( serializeJson( data ) );

	}

}

... we get the following output:

{ "MYQUOTEDKEY": "myValue", "MYKEY": "myValue" }

Notice that both Struct keys - quoted and unquoted - were converted to uppercase during the serializeJson() call. Setting this option to false overrides both the preserveCase attribute of the CFProcessingDirective tag as well as the Preserve Case setting within the Lucee Server Admin.

Ultimately, when it comes to Struct-keys and serializeJson() calls in Lucee ColdFusion, the best option is to just enable Preserve Case in the Lucee Server Admin. This will set the dot-notation-upper-case Compiler directive to false, which will allow all Struct-keys to adhere to the casing as it is defined within the code. This will make all of your serialized output more predictable, making Lucee CFML much more compatible with JavaScript-based clients that consume JSON via AJAX and embedded JSON payloads.



Reader Comments

Very useful exploration.

This is why I always write Structs with quoted keys and I always read from a Struct, in the same way, for consistency's sake:

bar = foo['bar'] ;

Rather than:

bar = foo.bar;

I am also leaning towards writing to linked hash maps.
So instead of:

foo = StructNew();

Or:

bar = {};

I like to use:

foo = createObject("java","java.util.LinkedHashMap").init();

In this way, I can guarantee the key order, although, confusingly:

writeDump();

Does not honour this order, when displayed to the screen, which can be a source of much confusion.

Reply to this Comment

@Charles,

So, regarding the struct-key ordering, one thing I saw when reading though the documentation is that you can pass a type to the structNew() method:

https://docs.lucee.org/reference/functions/structnew.html

It looks like doing structNew( "linked" ) will create a Struct that maintains the key-ordering. I haven't tried it yet, though.

You can also use a "linked" syntax that looks like an array, [key:value]. Again, I haven't tried this yet. Perhaps this weekend.

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
NEW: Some basic markdown formatting is now supported: bold, italic, blockquotes, lists, fenced code-blocks. Read more about markdown syntax »
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.