Understanding Struct Key-Casing Using SerializeJson() In Lucee 5.3.2.77
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
:
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 theserializeJson()
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.
Want to use code from this post? Check out the license.
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:
Rather than:
I am also leaning towards writing to linked hash maps.
So instead of:
Or:
I like to use:
In this way, I can guarantee the key order, although, confusingly:
Does not honour this order, when displayed to the screen, which can be a source of much confusion.
@Charles,
So, regarding the struct-key ordering, one thing I saw when reading though the documentation is that you can pass a
type
to thestructNew()
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.Awesome news! Another useful addition to the CFML language...
@Ben, @Charles. Yes
[ key: value ]
is the way to go for maintaining key order.@Ben,
You probably know this by now, but this will also create a struct that will maintain the order the key's are inserted in the struct
newStruct = [:];
@Frederic,
Oh heck yeah! I've been a big fan of ordered-structs since I realized that Lucee (and ACF) have this syntax. For the most part, in a production app it doesn't really matter what order keys are in. But, when doing demos with screen shots or outputting JSON, seeing the keys in a "human friendly" order is such a nice experience.