structKeyExists vs isNull vs ParameterExists vs isDefined vs elvis operator
structKeyExists vs isNull vs ParameterExists vs isDefined vs elvis operator
CFML has several ways to ask "is there a value here?". They give the same answer for an existing value, but they differ for a key or variable that holds null, and the answer can change with the Null Handling in CFML setting.
All results on this page were verified on Lucee 7.1.2.19 and 8.0.0.211 (both versions behave the same).
Test cases
s = {};
s.k = nullValue(); // key holding null
json = deserializeJSON( '{"k":null,"x":1}' ); // JSON null, same results as s.k
empty = { k: "" }; // key with an empty string
variables.v = nullValue(); // variable set to null
a = { x: 1 }; // a.b.c with b missing
Struct literals ({ k: nullValue() }, [ k: nullValue() ]) and assignments (s.k = nullValue()) behave the same.
Partial null support (default)
| Case | structKeyExists() |
isNull() |
parameterExists() |
isDefined() |
x ?: "default" |
x?.y |
x |
|---|---|---|---|---|---|---|---|
| struct key holding null | false | true | false | false | "default" |
null | error |
| missing struct key | false | true | false | false | "default" |
null | error |
struct key with "" |
true | false | true | true | "" |
"" |
"" |
| undefined variable | false | true | false | false | "default" |
– | error |
variables / local variable set to null |
false | true | false | false | "default" |
– | error |
nested a.b.c, b missing |
false (a, "b") |
true | false | false | "default" |
null | error |
With partial null support, a key holding null behaves like a missing key for every check, and reading it directly throws. The key is still stored in the struct, though: structCount() and structKeyList() include it, and serializeJSON() writes it as null.
s = deserializeJSON( '{"a":null,"b":1}' );
dump( structKeyExists( s, "a" ) ); // false
dump( isNull( s.a ) ); // true
dump( structCount( s ) ); // 2
dump( serializeJSON( s ) ); // {"a":null,"b":1}
Full null support
| Case | structKeyExists() |
isNull() |
parameterExists() |
isDefined() |
x ?: "default" |
x?.y |
x |
|---|---|---|---|---|---|---|---|
| struct key holding null | true | true | false | true | "default" |
null | null |
| missing struct key | false | true | false | false | "default" |
null | error |
struct key with "" |
true | false | true | true | "" |
"" |
"" |
| undefined variable | false | true | false | false | "default" |
– | error |
variables / local variable set to null |
true | true | false | true | "default" |
– | null |
nested a.b.c, b missing |
false (a, "b") |
true | false | false | "default" |
null | error |
With full null support, a key or variable holding null exists: structKeyExists() and isDefined() return true, and reading it returns null instead of throwing. isNull() is true either way.
Two things don't change with full null support:
- The elvis operator returns the default for null, so
s.k ?: "default"is"default"whetherkis missing or holds null. parameterExists()returns false for a null value.
For the isDefined() column above the name was passed as a literal string (isDefined( "s.k" )). With full null support, a name held in a variable (name = "s.k"; isDefined( name )) returned false for a null value in the same test, like parameterExists().
Elvis operator and safe navigation
The elvis operator ?: returns the right-hand side when the left-hand side is null or doesn't exist, without throwing. Safe navigation ?. stops at the first missing part of a path and returns null:
a = { x: 1 };
dump( a.b.c ?: "default" ); // "default"
dump( isNull( a?.b?.c ) ); // true
dump( a.b.c ); // error: key [B] doesn't exist
An empty string is a value, so empty.k ?: "default" returns "".
Recommendations
- To get a value or a fallback, use the elvis operator:
name = form.name ?: "anonymous";. It behaves the same in both null support modes. - To read a nested value that may be missing, use safe navigation:
city = user?.address?.city;. - To check for "no value", use IsNull(): it's true for null, missing keys and undefined variables in both modes. Use a scoped reference (
isNull( local.result )), see Null Handling in CFML. - To check whether a key exists, use StructKeyExists(), and keep in mind that a key holding null only counts as existing with full null support.
- To tell "key holds null" apart from "key is missing", you need full null support (
structKeyExists()true plusisNull()true). - Avoid IsDefined() for null checks: its result depends on the null support mode and, with full null support, on whether the name is a literal string.
- ParameterExists() is a legacy function that calls
isDefined()internally. UsestructKeyExists(),isNull()or the elvis operator instead.