Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at cf.Objective() 2011 (Minneapolis, MN) with:

ColdFusion ARGUMENTS Keys Always Exist, Even When Not Required

By Ben Nadel on
Tags: ColdFusion

I ran into a little issue yesterday with optional arguments in a ColdFusion user defined function. I kept getting a "key does not exist as referenced as part of structure" or some such error like that (I can't quite remember it exactly). I tried to duplicate this issue below, but am not quite getting the same error:

  • <cffunction
  • name="SumArguments"
  • access="public"
  • returntype="numeric"
  • output="true">
  •  
  • <!--- Define arguments. --->
  • <cfargument name="One" type="numeric" required="false" />
  • <cfargument name="Two" type="numeric" required="false" />
  • <cfargument name="Three" type="numeric" required="false" />
  • <cfargument name="Four" type="numeric" required="false" />
  •  
  • <!--- Define the local scope. --->
  • <cfset var LOCAL = {} />
  •  
  • <!--- Create a running total. --->
  • <cfset LOCAL.Sum = 0 />
  •  
  • <!---
  • Loop over argument scope so that we only use the
  • arguments that were defined by the calling code.
  • --->
  • <cfloop
  • item="LOCAL.Key"
  • collection="#ARGUMENTS#">
  •  
  • <!--- Add argument to running total. --->
  • <cfset LOCAL.Sum += ARGUMENTS[ LOCAL.Key ] />
  •  
  • </cfloop>
  •  
  • <!--- Return sum. --->
  • <cfreturn LOCAL.Sum />
  • </cffunction>
  •  
  •  
  • <!--- Output sum. --->
  • #SumArguments( One = 1, Two = 3, Three = 5 )#

Notice here that I am passing in three out of the four arguments to my SumArguments() method. Then, inside the method, I am looping over the ARGUMENTS keys and adding the argument to the running total. I thought that the collection-based CFLoop would protect me from optional arguments; but, when we run the above code, we get the following error:

null null

The reason behind this, as I found out, is that the keys in the ARGUMENTS scope always exist. And, in fact, if you dump out the key list:

  • StructKeyList( ARGUMENTS )

... for the above method, you always get this:

FOUR,THREE,ONE,TWO

Now, while the keys always exist, the values do not! When we CFDump out the ARGUMENTS scope, we get this:

 
 
 
 
 
 
ColdFusion's ARGUMENTS Scope Always Has All Keys. 
 
 
 

As you can see, the value in "Four" is an "undefined struct element".

When we see that ARGUMENTS keys always exists, we need to modify our ColdFusion user defined function to check for struct-value existence before usage:

  • <cffunction
  • name="SumArguments"
  • access="public"
  • returntype="numeric"
  • output="true">
  •  
  • <!--- Define arguments. --->
  • <cfargument name="One" type="numeric" required="false" />
  • <cfargument name="Two" type="numeric" required="false" />
  • <cfargument name="Three" type="numeric" required="false" />
  • <cfargument name="Four" type="numeric" required="false" />
  •  
  • <!--- Define the local scope. --->
  • <cfset var LOCAL = {} />
  •  
  • <!--- Create a running total. --->
  • <cfset LOCAL.Sum = 0 />
  •  
  • <!--- Loop over argument scope to get keys. --->
  • <cfloop
  • item="LOCAL.Key"
  • collection="#ARGUMENTS#">
  •  
  • <!---
  • The ARGUMENTS keys always exists, so, inside of the
  • collection loop, we still need to check for the
  • existence of the actual argument.
  • --->
  • <cfif StructKeyExists( ARGUMENTS, LOCAL.Key )>
  •  
  • <!--- Add argument to running total. --->
  • <cfset LOCAL.Sum += ARGUMENTS[ LOCAL.Key ] />
  •  
  • </cfif>
  •  
  • </cfloop>
  •  
  • <!--- Return sum. --->
  • <cfreturn LOCAL.Sum />
  • </cffunction>
  •  
  •  
  • <!--- Output sum. --->
  • #SumArguments( One = 1, Two = 3, Three = 5 )#

When we run this code, we get the following:

9

This is a little funny because our safety net in this version is that we run a StructKeyExists() on the ARGUMENTS scope before using the given argument; however, we just discovered that the keys in the ARGUMENTS scope always do exist! It is seemingly a contradiction. However, we have to realize that StructKeyExists() is not simply looking at the keys. StructKeyExists() is NOT the logical equivalent of:

  • ListFind( StructKeyList( Scope ), Key )

Really, perhaps a more appropriate name for the StructKeyExists() method would be StructValueExists( Scope, Key ); because, in reality, that is what ColdFusion is testing. Of course, at the end of the day, StructKeyExists() doesn't really confuse anyone and it works wonderfully - I'm not actually suggesting that its name be changed; this post was just meant to point out that the keys in the ColdFusion ARGUMENTS scope always exist, even for optional arguments.




Reader Comments

I ran into a similar issue when converting some remote objects into web services in flex 3. Web services recognized the arguments as defined even if required="false".

@John,

Web services and optional arguments adds a whole other layer of complexity, as you probably found out. Now, you have to deal with adhering to the WSDL specifications :) It took me a while to figure out that you have to use the OMIT attribute on optional arguments.

This is interesting.

I recently built a gateway with a few specified but not required arguments. In development, I dumped the arguments scope to check what they looked like and remember seeing the "not set" values. I never really gave it a second thought that my structKeyExists test ran as I intended, even though I knew those struct keys did exist...

This forced me to go back and run some tests to validate it for myself. Good post!

@Johans,

The problem with specifying a default is that you cannot omit an argument since there is then no way to tell if the calling code provided a value.

Understand - I have never has cause to need to know if the argument was passed as my methods use the argument default if no value is passed.

Good to know anyway.

@Johans,

The situation I ran into the other day was that I had a method that took an optional argument whose data type was of a complex nature (it was actually a ColdFusion component data type). Therefore, if one was not passed-in, I would have no way to even create a default value.

But, yes, 99% of the time, I generally do just default arguments to their most usable default values.

If you have default values, but need to tell if the argument was passed in or not, you could define a struct with those values inside the function, do the tests to see if the values exist, and then do the non-destructive StructAppend(). Its a hack, but it does work.

That is an interesting bug you found Ben. I mean, I'd think its a bug, rather then intentional. The list of keys should match with the results of StructKeyExists() tests, in my mind, or at least show up in a dump of the struct.

@Jon,

I am not sure if it is a bug. I am leaning towards no. The thinking behind this is that if you store a "null" value into a struct:

REQUEST.Value = JavaCats( "null", 0 )

You will get the same behavior (the struct key is there with an "undefined struct value" memeber). It looks like this is simply how ColdFusion handles Java null values (which is what I assume happens to optional arguments - just like Javascript arguments that are not passed in).

@Ben

I can follow that, but ColdFusion doesn't really have a concept of null values, so I think its odd to have an unexpected behavior based on interactions with null values.

Either it shouldn't deal with nulls at all or it should behave like it does with null values in query sets (return an empty string). I think that your point about StructKeyExists() points to this as well; if the key exists, the value should exist, and if the value does not exist, the key should not exist.

Can you come up with a reason why you would want ColdFusion to behave as it does right now?

@Jon,

I suppose you are correct. Let's put it this way - there is NO benefit from what I can see to having a key exist with no value; especially when it will still fail StructKeyExists(). Perhaps the error is in the Collection Loop?

I'd imagine that <cfloop collection="#struct#" item="key" /> is executed similarly to <cfloop list="#StructKeyList(struct)#" index="key" />, so I'd say that the problem is probably that StructKeyList() returns the keys with null values.

That is very interesting. Yes, an undefined VALUE, but key, if set in args, always exists. Our code house has slowly drifted to overloaded args for optionals (when not needed merely for self-documentation purposes/default values) as well as traditionally using isDefined('scope.value') instead of structKeyExists which both seem to really see if the VALUE is defined, not just the key. [Yes yes, we know the debate about isDefined vs SKE speed/overhead. We don't see the impact in our testing as long as you scope it]. Combined with argumentCollection to call functions, we rarely run into this type of an issue.

@William,

Just so I am not misreading what you are saying, StructKeyExists() *does* check to see if the value exists, just like IsDefined(). The issues seems to lay in the StructKeyList() type method, which is unrelated.

@Ben,

Yes, that is correct. structKeyExists() and isDefined() essentially do the SAME thing as long as you scope your variable in isDefined. They both check the value. Your issue is more how to dynamically loop across argument keys/values when some of them are effectively null (how to determine and handle the null when it comes through with the extant keys). This is where argumentCollection comes in handy because you could pass in a structure or array as an arg which would have to have all its values assigned and you simply loop across that, rather than hardcoded individual arguments. No limit to how many it will sum, etc. However, you could do that w/o function arraySum().... but for sake of example, the arg-collection lets you get around the key-null issue.

@William,

Not to get picky about the terminology, but I don't think argumentCollection helps us out here since it seems to work pre-method signature (meaning, from inside the function, it appears as if the arguments were all passed in separately).

However, your idea about passing in a single struct as a *single* argument, would indeed help since there is no sense of omitted arguments.

Here is how I would get around the predefined/null issue. You can still do a dyanmic key based loop but I had to resort evaluation() which I don't like (maybe there is a another way with it?) but I would simply design around the issue.

<cffunction
name="SumArguments"
access="public"
returntype="numeric"
output="true">

<cfargument name="a_Array" type="array" required="false" />

<cfscript>
var local = structNew();
var i = 0;
local.status = 0;
local.sum = 0;

try{
for(i=1; i < arguments.a_Array.size(); i++){
local.sum += arguments.a_Array[1];
}
} catch (any err){
local.status = 2;
writeoutput('in catch ');
utl.dump(err);
// error handling call goes here
return -999;
}
local.status = 1;
return local.sum;
</cfscript>
</cffunction>

<cfscript>
myArray = arrayNew(1);
myArray[1] = 1;
myArray[2] = 8;
myArray[3] = 9;
myArray[6] = 3;
myArray[7] = 21;
myArray[8] = 4;

// named collection structure
argsSum.a_Array = myArray;
x = sumArguments(argumentCollection: argsSum);

// or since there is only one arg
y = sumArguments(myArray);

utl.dump(x);
utl.dump(y);

</cfscript>

I was thinking about this a bit more. I just picked up the tone that the fact the keys come in when there is no value may be incorrect.

I would tend to the side that this *is* correct functionality. They are defined (the keys) by the fact you have pseudo-param'ed them by stating the argument; they just don't necessarily have a value defined (effectively 'null') if optional. This seems correct to me, as designed by Adobe, as well.

So, yeah, you have to work around that one way or the other: by design, or by catching/checking for it.... (not too bad to add extra check on them in cfloop, a little harder in script).

@William,

Yeah, I don't think it's an error either. In all fairness, this is something that has only caused me issues once - it's not an issue that would even need to be coped with all that often. Definitely a small minority of cases.

Ben a had a similar trouble to populate or not TransferObject (init method).

See my code :

<cfscript>
// method with four arguments above
var local = {};
var Result = '';
var DTO = getTO('FAQsTO');

for(key in arguments)
if(len(trim(arguments[key])))
local[key] = len(trim(arguments[key]));

if(NOT StructIsEmpty(vArgs) )
DTO.init(ArgumentCollection=local);

...
</cfscript>

in Your case could be :

<cfscript>
for(key in arguments)
local[key] += val(arguments[key]);
</cfscript>

Jeff

Dunno what the direct correlation is, if any, but there is a 'null' in CF under certain circumstances. In the following code, if there is an error instantiating the object, then the reference will not exist, although the variable did (clearly) exist beforehand. It's effectively set to null when the object call fails.

var myObj = "";
var result = "";

myObj = createObject("component", "Widget").init();
result = myObj.getResult();

If there was a trapped error on init() to begin with, then the code will fail when it attempts myObj.getResult() because the object simply doesn't exist. Not sure I've explained it well, but I think it's probably similar with the arguments collection: the keys exist, but they may be truly 'null', at the Java level, and I'm guessing that structKeyExists() then distinguishes only the non-null keys.

@Jason,

I understand you, I think. When we interact with Java methods a lot, if Java returns a null value then the key will exist in the calling scope, but will have the undefined value.

That is why, when we interact with Java, it is a best practice to scope your return variable if it is excepted that a null value might come back:

<cfset REQUEST.ReturnValue = JavaObject.Method() />

<cfif StructKeyExists( REQUEST, "ReturnValue" )>

... java method was called and returned value ...

</cfif>

@Jason, I just experienced the 'null' of which you speak today. Effectively, a previously set key (x.x = ''; which is our coldfusion pseudo non-value init, wouldn't it be neat if we could just do this var x.x; ) then is set to x.x = application.myObjects.someFunction(argumentCollection: args);
and if that fails, the value is GONE. (throws undefined error). This only seems to occur if the object called has a pretty serious error that is untrapped (now that I have fixed the cfc, I am finding it hard to replicate in my attempts to 'break' the cfc code). Even outside my internal traps, CF catches the basics and tells me the issue. Anyway, now I have seen this for myself and it reminded me of this thread. Makes me entertain the idea of creating a seriously flawed object to allow me to set true 'nulls'.. ha ha. However, the same issues still really exist in CF in as much you can't really get a handle on a 'null' in CF except by h-nought testing.

I am literally 4 years too late on this topic...BUT, I ran into a similar issue on Railo.

However, isDefined() works just fine when checking for arguments supplied to a function.

public any function init(string myOptionalArg) {

if ( isDefined("arguments.myOptionalArg") ) {

return true;

}

return false;

}