Skip to main content
Site is running in a degraded state. I'm working with Hostek to figure out what broke.
Ben Nadel at InVision In Real Life (IRL) 2019 (Phoenix, AZ) with: Edith Au
Ben Nadel at InVision In Real Life (IRL) 2019 (Phoenix, AZ) with: Edith Au

Core Decision Functions Will Accept Null / Undefined Values In ColdFusion

By
Published in Comments (4)

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

32 Comments

I have definitely noticed this behavior and this really does clarify it. I'll get a bug report about using:

(forget to define)
if( len(local.EmailAddress))...

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.)

15,759 Comments

@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 return 0 (at least in Lucee which is what I have running at this moment):

len( local?.EmailAddress )

Note the ?. operator.

It's fun stuff 😜

32 Comments

@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.)

15,759 Comments

@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.

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel