Core String Functions Will Accept Null / Undefined Values In ColdFusion
In ColdFusion, you can have null / undefined values. You can't directly reference those null values or you'll get a null reference error (NRE); but, you can pass around null values using return-value propagation or the safe-navigation operator. This sometimes leaves me unsure as to how ColdFusion will handle a specific null-value scenario. A while back, I looked at ColdFusion's core decision functions; and it turns out, they all play nicely with undefined values. Today, I wanted to look at the core string functions in ColdFusion to see how they handle null / undefined values.
In a lot of old ColdFusion code, you'll see very cautious traversal of object paths using logic that looks like this:
<cfscript> | |
if ( variables.keyExists( "value" ) && len( variables.value ) ) { | |
// ... do something with value .... | |
} | |
</cfscript> |
In this case, we're using the structKeyExists()
function to see if the property value
exists before subsequently inspecting its length so we don't throw a null reference error. In modern ColdFusion, we can often use the Elvis operator (?:
) and / or the safe navigation operator (?.
) to simplify our logic. However, since ColdFusion hasn't always played nicely with null / undefined values, it's not always clear what happens when ColdFusion consumes a null value in a function call like len()
.
It turns out, ColdFusion does exactly what we would hope that it does: treat the value as "empty". That allows us to simplify the above code like this:
<cfscript> | |
if ( len( variables?.value ) ) { | |
// ... do something with value .... | |
} | |
</cfscript> |
It turns out that a lot of the native string-oriented functions in ColdFusion do exactly what we would hope that they do. Here's a quick sanity check - I'm going to consume a null / undefined value in a bunch of ColdFusion functions that assume strings:
<cfscript> | |
try { | |
echoVersion(); | |
echoValue( "val: [#val( variables?.undefined )#]" ); | |
echoValue( "trim: [#trim( variables?.undefined )#]" ); | |
echoValue( "len: [#len( variables?.undefined )#]" ); | |
echoValue( "ucase: [#ucase( variables?.undefined )#]" ); | |
echoValue( "lcase: [#lcase( variables?.undefined )#]" ); | |
echoValue( "left: [#left( variables?.undefined, 1 )#]" ); | |
echoValue( "right: [#right( variables?.undefined, 1 )#]" ); | |
echoValue( "mid: [#mid( variables?.undefined, 1, 1 )#]" ); | |
echoValue( "listLen: [#listLen( variables?.undefined )#]" ); | |
echoValue( "listFirst: [#listFirst( variables?.undefined )#]" ); | |
echoValue( "listRest: [#listRest( variables?.undefined )#]" ); | |
echoValue( "getToken: [#getToken( variables?.undefined, 1 )#]" ); | |
echoValue( "encodeForHtml: [#encodeForHtml( variables?.undefined )#]" ); | |
echoValue( "encodeForHtmlAttribute: [#encodeForHtmlAttribute( variables?.undefined )#]" ); | |
echoValue( "encodeForUrl: [#encodeForUrl( variables?.undefined )#]" ); | |
echoValue( "asc: [#asc( variables?.undefined )#]" ); | |
echoValue( "hash: [#hash( variables?.undefined )#]" ); | |
echoValue( "toString: [#toString( variables?.undefined )#]" ); | |
echoValue( "canonicalize: [#canonicalize( variables?.undefined, true, true )#]" ); | |
echoValue( "concatenation: [#( variables?.undefined & variables?.undefined )#]" ); | |
echoValue( "equality: [#( variables?.undefined == variables?.undefined )#]" ); | |
echoValue( "inequality: [#( variables?.undefined != variables?.undefined )#]" ); | |
// Error: Can't cast string to boolean. | |
// echoValue( "logical: [#( variables?.undefined && variables?.undefined )#]" ); | |
} catch ( any error ) { | |
writeDump( error ); | |
} | |
// ------------------------------------------------------------------------------- // | |
// ------------------------------------------------------------------------------- // | |
/** | |
* I output the current ColdFusion product version. | |
*/ | |
public void function echoVersion() { | |
var version = ( server.keyExists( "lucee" ) ) | |
? "Lucee CFML #server.lucee.version#" | |
: "Adobe ColdFusion #server.coldfusion.productVersion#" | |
; | |
echoValue( version ); | |
echoValue(); | |
} | |
/** | |
* I output the given value on its own line. | |
*/ | |
public void function echoValue( any value = "" ) { | |
writeOutput( value & "<br />#chr( 10 )#" ); | |
} | |
</cfscript> |
If we run these in Lucee CFML 6.2, we get the following output:
Lucee CFML 6.2.1.122 | |
val: [0] | |
trim: [] | |
len: [0] | |
ucase: [] | |
lcase: [] | |
left: [] | |
right: [] | |
mid: [] | |
listLen: [0] | |
listFirst: [] | |
listRest: [] | |
getToken: [] | |
encodeForHtml: [] | |
encodeForHtmlAttribute: [] | |
encodeForUrl: [] | |
asc: [0] | |
hash: [D41D8CD98F00B204E9800998ECF8427E] | |
toString: [] | |
canonicalize: [] | |
concatenation: [] | |
equality: [true] | |
inequality: [false] |
If we run these in Adobe ColdFusion 2023, we get the following output:
Adobe ColdFusion 2023,0,13,330759 | |
val: [0] | |
trim: [] | |
len: [0] | |
ucase: [] | |
lcase: [] | |
left: [] | |
right: [] | |
mid: [] | |
listLen: [0] | |
listFirst: [] | |
listRest: [] | |
getToken: [] | |
encodeForHtml: [] | |
encodeForHtmlAttribute: [] | |
encodeForUrl: [] | |
asc: [0] | |
hash: [null] | |
toString: [] | |
canonicalize: [] | |
concatenation: [] | |
equality: [YES] | |
inequality: [NO] |
I tried running this on BoxLang v1.0.0, but get an error:
java.lang.VerifyError: Inconsistent stackmap frames at branch target 2282
As you can see, ColdFusion basically treats null / undefined inputs the same way it would treat the empty string; which is exactly what we would hope that it does when attempting to simplify our logic. In particular, I'm excited to know that I can use len()
and val()
to safely consume a lot of potentially undefined values in my ColdFusion control flow.
Want to use code from this post? Check out the license.
Reader Comments
While I like most of it (variables?.undefined) I can't say I'm a fan of hash() returning a value. I can see potential issues there. I think CF gets it "more better" here. It was funny, I immediately said "noooooo" as I was scrolling to change to "ah, that's better" seeing the CF result.
For me, it's ACF for work and 100% Lucee for non-work or anything "new" like a personal biz. (And by "100%" Lucee I mean "if I'm not using non-CFML" like a JS framework.)
@Will,
Yeah, the
hash()
one struck me as strange too on the Lucee side. I wonder if it's converting to an empty string; or maybe the string literalnull
. I'll see if I can sus-out which thing it's doing.re: side-project stuff, I'm the opposite - Lucee at work, ACF in personal life. That's primarily because I use managed hosting. If I actually knew something about server management, I would have more options.
Are you managing your own servers?
Post A Comment — ❤️ I'd Love To Hear From You! ❤️