Core Decision Functions Will Accept Null / Undefined Values In ColdFusion
ColdFusion has always had a contentious relationship with null / undefined values. The ColdFusion runtime supports null and undefined values; but, if consumed incorrectly, such values lead to null reference errors. As such, it isn't always clear where you can and can't use undefined values. Today, I want to demonstrate that all of the core decision functions (such as isSimpleValue()
, isArray()
, and isStruct()
) will accept null / undefined values; and, will do pretty much what you hoped they would do.
But first, we have to differentiate between referencing a null value and passing a null value. And, this is where much of the confusion (and runtime errors) come from.
When you "reference" a null value, you're trying to consume a variable (or key) that points to an undefined value. For example, if the variable total
is undefined, then the following line of CFML will throw an error:
writeOutput( total )
This results in the error:
Variable TOTAL is undefined.
However, if you define a user defined function (UDF)—getTotal()
—that returns an undefined value, the following code will execute without error:
writeOutput( getTotal() )
The difference here is that the former code references an undefined value and this latter line of code merely passes an undefined value. ColdFusion is totally OK with undefined values being passed around.
Aside: Some expressions, like
isNull( total )
, do not throw an error because the code is actually transformed during the template compilation process. As such, the resultant runtime code—unbeknownst to us—contains safer expression logic.
Which brings us back to ColdFusion's decision functions. All of the core decision functions can accept a null / undefined value so long as the function argument doesn't lead to a null reference error.
In other words, this will still throw an error for a null value:
isNumeric( total )
But, this will simply return false
for a passed-in null value:
isNumeric( getTotal() )
The easiest way to demonstrate this is with the safe navigation operator (?.
). The safe navigation operator checks both the left and right operands and results in null / undefined—rather that raising a null reference error exception—if either of the operands are null / undefined. We can therefore use the expression:
variables?.oopsy
... to pass around an undefined value without raising a null reference error:
<cfscript>
try {
// These functions all work the same in Adobe ColdFusion (ACF) and Lucee CFML.
writeOutput( isArray( variables?.oopsy ) );
writeOutput( isBinary( variables?.oopsy ) );
writeOutput( isBoolean( variables?.oopsy ) );
writeOutput( isClosure( variables?.oopsy ) );
writeOutput( isCustomFunction( variables?.oopsy ) );
writeOutput( isDate( variables?.oopsy ) );
writeOutput( isJson( variables?.oopsy ) );
writeOutput( isNumeric( variables?.oopsy ) );
writeOutput( isNumericDate( variables?.oopsy ) );
writeOutput( isQuery( variables?.oopsy ) );
writeOutput( isSimpleValue( variables?.oopsy ) );
writeOutput( isStruct( variables?.oopsy ) );
writeOutput( isXml( variables?.oopsy ) );
writeOutput( isXmlAttribute( variables?.oopsy ) );
writeOutput( isXmlDoc( variables?.oopsy ) );
writeOutput( isXmlRoot( variables?.oopsy ) );
writeOutput( lsIsDate( variables?.oopsy ) );
writeOutput( lsIsNumeric( variables?.oopsy ) );
// Returns true in ACF, returns false in Lucee.
writeOutput( isObject( variables?.oopsy ) );
// Returns false in ACF, function not available in Lucee.
// writeOutput( isDateObject( variables?.oopsy ) );
// Returns false in ACF, throws an error in Lucee.
// writeOutput( isFileObject( variables?.oopsy ) );
// Returns false in Lucee, throws an error in ACF.
// writeOutput( lsIsCurrency( variables?.oopsy ) ); // Throws an error.
// Returns true in ACF, throws an error in Lucee.
// writeOutput( structIsEmpty( variables?.oopsy ) );
} catch ( any error ) {
writeDump( error );
}
</cfscript>
This CFML code executes without error in both Adobe ColdFusion 2023 and Lucee CFML 6. And, with the exception of isObject()
being different in the two runtimes, all of the core decision functions return false
for an undefined argument.
What this means is that compound conditions that start with a null-check and then contain a decision function like:
! isNull( value ) && isStruct( value )
... can actually be combined with something like the safe navigation operator to be a single call:
isStruct( variables?.value )
... where variables
might also be the arguments
scope, the local
scope, the url
scope, the form
scope, the cookie
scope, etc.
Aside: Fun fact, the
cgi
scope always returns a string even when you try to reference an undefined key (ex,cgi.oopsy
).
Want to use code from this post? Check out the license.
Reader Comments
I have definitely noticed this behavior and this really does clarify it. I'll get a bug report about using:
in code I wrote the same day as
len(local.User.getEmailAddress())
which works fine. (Or something similar.)
Also, almost all of our sql query params (we're 100% stored proc), we use
...null="#isNull(arguments.Criteria.getPersonID()))#"
Sometimes a simple "false" when the proc arg is required and won't ever be changing. (Yes, it's CFML, not script. After 25 years, I feel weird without the ="#val#" quotes setup.)
@Will,
Honestly, I still write a lot of my data access layer in tags because I like the
<cfqueryparam>
ergonomics so much; and the fact that it is literally inline with the SQL statement itself (collocation of behavior is so powerful from a cognitive standpoint). All to say, you're in a safe space for talking about tags 😉To pile-on to the example you give, while this throws an error:
len( local.EmailAddress )
.. for an undefined
EmailAddress
property, this will return0
(at least in Lucee which is what I have running at this moment):len( local?.EmailAddress )
Note the
?.
operator.It's fun stuff 😜
@Ben Nadel,
Huge fan of Lucee, but we don't use it at work. :(
All my personal projects, though, any any self-business, is definitely Lucee. CommandBox prefers it, as well.
Also, when it comes to work stuff, I'd rather be verbose than clever when it comes to some things. Even using ?: can be "not as obvious" as just writing out an if/else. I'm always trying to think about "the next guy" who has to look at my code. (Not a "job security" mindset, but makes me feel better about myself.)
@Will,
Totally get it. It's all a balancing act between terseness and readability. I don't always know the best path forward - I'll often try something, get a feel for it, and then possible change it back cause I didn't like it overall.
Post A Comment — ❤️ I'd Love To Hear From You! ❤️
Post a Comment →