Struct Keys That Contain Dots "Just Work" In Adobe ColdFusion
Over the weekend, one of the CFML features that I explored in my Adobe ColdFusion 2025 hackathon entry was the use of a .properties
file. A .properties
file is just a simple list of key-value pairs that ColdFusion will read into a struct. In my exploration, I had defined one of the keys as, team.name
. And, I was surprised that addressing the key without quotes "just worked" in my CFML. This does not work in Lucee CFML; but, apparently, this has worked in Adobe ColdFusion since... forever?
This is not the first time that I've seen this kind of behavior in Adobe ColdFusion. From within ColdFusion custom tags, Adobe has always allowed the caller
scope to seamlessly access contextual data using a dot-delimited key. Presumably, this is how native custom tags like CFSaveContent
allow for "key paths" to work in the variable
attribute. Example:
<cfsavecontent variable="local.buffer">
In this case, the CFSaveContent
custom tag is capturing the generated content of the custom tag and storing it into the function-local variable, buffer
. If we needed to implement this ourselves in Adobe ColdFusion, we could just do:
caller[ attributes.variable ] = thistag.generatedContent
Which would translate into:
caller[ "local.buffer" ] = thistag.generatedContent
... which would properly set the buffer
key within the local
scope of the calling context.
Aside: this does not work in Lucee CFML. However, in Lucee CFML, you can use either
cfparam
orsetVariable()
to accomplish the same thing in a ColdFusion custom tag.
Now, for years, I assumed that this was something special about the caller
scope in a custom tag context. What I didn't realize (or didn't remember) until the hackathon was that this was kind of a generalized feature of Adobe ColdFusion.
To demonstrate, I'm going to use an .ini
file (since I wanted to test this in Adobe ColdFusion 2021, which doesn't support .properties
):
[first.section]
key=value
sub.key=sub.value
[second.section]
key=value
sub.key=sub.value
In this .ini
file, the first.section
and second.section
are atomic keys. And, within those sections, the sub.key
is also an atomic key. Normally in CFML, we'd have to address these keys using bracket-notation:
[ "first.section" ][ "sub.key" ]
... which would work in all CFML engines. However, in Adobe ColdFusion specifically, we can just use:
first.section.sub.key
To demonstrate, I'll read-in this .ini
file and do exactly that:
<cfscript>
include "./mixins.cfm";
// ------------------------------------------------------------------------------- //
// ------------------------------------------------------------------------------- //
data = getProfileData( expandPath( "./data.ini" ) );
dump( data );
// We know this will work in all CFML engines, since this is just normal key look-up.
echoLine( data[ "first.section" ][ "sub.key" ] );
try {
echoLine( data.first.section.sub.key );
} catch ( any error ) {
echoLine( "Accessing [data.first.section.sub.key] failed." );
echoLine( error.message );
}
</cfscript>
If we run this in both Adobe ColdFusion 2021 and Lucee CFML, we get the following output:

As you can see, Adobe ColdFusion 2021 had no problem addressing the nested value using keys-with-dots. However, Lucee CFML throws an error.
As a code kata, I wanted to see if could "normalize" this behavior with a user defined function (UDF), structAccess()
. The goal here would be to achieve the same behavior in both engines.
<cfscript>
include "./mixins.cfm";
// ------------------------------------------------------------------------------- //
// ------------------------------------------------------------------------------- //
data = getProfileData( expandPath( "./data.ini" ) );
dump( data );
// Normalized access in both CFML engines.
dump( structAccess( data, "first.section.sub.key" ) );
// ------------------------------------------------------------------------------- //
// ------------------------------------------------------------------------------- //
/**
* I walk the target sturcture, looking the given path (only works with dots).
*/
public any function structAccess(
required struct target,
required string objectPath
) {
var segments = objectPath.listToArray( "." );
var path = "";
var key = "";
// While we have segments, we're going to build up a complex, dot-delimited key
// until we find one that matches within the current target.
while ( segments.isDefined( 1 ) ) {
// Append next segment to the working path.
path &= ( "." & segments.shift() );
key = path.right( -1 );
if ( target.keyExists( key ) ) {
target = target[ key ];
path = key = "";
}
}
// We only want to return a target if we fully consumed the last key.
if ( ! key.len() ) {
return target;
}
}
</cfscript>
In the structAccess()
function, we split the object path on the .
character. And then, we keep building up the "next key", one segment at a time, until we find a key that matches within the current target. This allows both Adobe ColdFusion and Lucee CFML to work the same way:

As you can see, when using structAccess()
, both Adobe ColdFusion and Lucee CFML were able to access the nested value with the funky path.
This functionality creates ambiguity; but, I have to say, I kind of like it.
As a final note, there is no getProfileData()
in ColdFusion - it only has getProfileSections()
and getProfileString()
. My getProfileData()
usage above is just a user defined function to hide-away the complexity of using a .ini
file:
<cfscript>
/**
* I load the full INI file contents into the resultant data structure.
*/
public struct function getProfileData( required string filepath ) {
// The profile sections are returned as a struct in which each key is a list of
// keys available within the given section. We're going to map that back onto a
// nested structure.
return getProfileSections( filepath ).map(
( section, keys ) => {
var data = [:];
for ( var key in keys.listToArray() ) {
data[ key ] = getProfileString( filepath, section, key );
}
return data;
}
);
}
/**
* I wrap echo output in a div for spacing (in Adobe ColdFusion).
*/
public string function echoLine( required string value ) {
return echo( "<div> #value# </div>" );
}
// ------------------------------------------------------------------------------- //
// ------------------------------------------------------------------------------- //
// Polyfills for Lucee dumpers.
if ( ! getFunctionList().keyExists( "echo" ) ) {
echo = ( value ) => writeOutput( value );
dump = ( value ) => writeDump( var = value, argumentCollection = arguments );
}
</cfscript>
Epilogue on BoxLang
Out of curiosity, I wanted to see where BoxLang's CFML engine would come down on the matter. So, I put this into Try BoxLang:
data = {
"sub.key": "value"
};
dump( data.sub.key );
And it outputs a similar error as Lucee CFML:
The key
[sub]
was not found in the struct. Valid keys are ([sub.key]
).
So, it seems this is dot-key behavior is specific to Adobe ColdFusion.
Want to use code from this post? Check out the license.
Reader Comments
Hi Ben!
I found that Lucee can do some serious acrobatics with struct keys.
When wrestling with the OpenAPI spec I stumbled into using
StructKeyTranslate()
andSetVariable()
to do some heavy lifting.I planned to write something on the Lucee Dev site based on the
notes
I'd made. Ever hear the joke about laughter and plans?I put my chaotic notes into this Gist. It's a little hard to follow but I think you'll appreciate the 🤪🤔🤓factor!
https://trycf.com/gist/61610b14f7b58e3b5d37f23e096912b5/lucee5?theme=monokai
It works in Lucee 5+.
Cheers!
RayV
Post A Comment — ❤️ I'd Love To Hear From You! ❤️
Post a Comment →