Search code examples
structnullcoldfusion

ColdFusion: structFind failing but key appears to be correct


I can't figure out why my find is not working.

I have a structure created from deserializing some JSON. I get the key list from the structure and then try to find each value. 2 of the keys from the key list don't seem to exist.

The replace function is because I had to replace the %22 character encodings with ~ because it kept getting decoded midflight. That replace is just to put the double quotes back.

<cfscript>
    jsonIn = deserializeJSON(replace(result,"~","""","all"));
    typeList = "";
    for(x=1;x lte listLen(structKeyList(jsonIn[1]));x++){listAppend(typeList,"VarChar");}
    result = queryNew(structKeyList(jsonIn[1]),"");
    for(row in jsonIn) {
        queryAddRow(result);
        writeDump(row);
        for(cell in structKeyList(row)) {
            writeOutput(cell & " found: " & structKeyExists(row,cell) & "<br/>");
            //querySetCell(result, cell, row.find(cell));
        }
    }
writeDump(result);
</cfscript>

This is what I see in output:

diagnostic display

If I do a compare in those two cases I get a "-1", but I can't see why the strings are different (or how they can be since both are recovered by pulling from the key list of the object.)

EDIT

The error message is misleading. The problem is in the "undefined" values for those two keys. Changing the json "null" to space solved it.


Solution

  • Welcome to null in ColdFusion. Let me explain why a key does and does not exist.

    NULL in Query

    First of all, let's see why many developers new to CFML are confused. One of the first things you learn is:

    <cfquery>
        SELECT NULL AS cellWithNull;
    </cfquery>
    

    will turn out as

    cfdump query

    Reason

    ColdFusion has no native support for NULL and will treat this value as an empty string.

    null in Java

    At some point, you might need to pass a real null value to a Java method.

    // Java
    x = new org.example.FooBar();
    x.someMethod(null);
    

    Yet if you try to use or define a variable with null in CFML, such as:

    y = null;
    

    ColdFusion will throw an exception: Variable NULL is undefined.

    Reason

    ColdFusion has no native support for null. In fact, it does not even recognize null and will attempt to resolve it as a variable by name instead.

    // this will work
    null = "";
    y = null;
    // y = [empty string]
    

    Solution

    // ColdFusion
    x = createObject("java", "org.example.FooBar").init();
    x.someMethod(javaCast("null", ""));
    

    javaCast("null", "") exists for this use case. (The second argument has no meaning btw.)

    null in JSON (object)

    x = deserializeJSON('{ "key": null }');
    

    will turn out as

    cfdump json-object

    Reason

    ColdFusion has no native support for ... wait, what? Exactly, when deserializing null, the value will be treated as undefined, making the key to the value exist and not exist at the same time.

    structKeyList(x)
    >> "key"
    
    structKeyArray(x)
    >> [ "key" ]
    
    structKeyExists(x, "key")
    >> false
    
    isNull(x.key)
    >> true
    
    isNull(x["key"])
    >> true
    
    x.key
    >> Exception: Element KEY is undefined in X.
    
    writeDump(x["key"])
    >> undefined
    
    writeOutput(x["key"])
    >> [empty string]
    

    Solution

    When iterating over keys of a struct, always check if the value behind the key is "defined" according to ColdFusion's standard. Use either structKeyExists or isNull to check the value.

    null in JSON (array)

    x = deserializeJSON('[ null ]');
    

    will turn out as

    cfdump json-array

    Reason

    You know the drill by now.

    arrayLen(x)
    >> 1
    
    isNull(x[1])
    >> Exception: Element 1 is undefined in a Java object of type class coldfusion.runtime.Array.
    
    writeDump(x[1])
    >> Exception: Element 1 is undefined in a Java object of type class coldfusion.runtime.Array.
    
    writeOutput(x[1])
    >> Exception: Element 1 is undefined in a Java object of type class coldfusion.runtime.Array.
    
    arrayIsDefined(x, 1)
    >> false
    

    Solution

    Always check with arrayIsDefined before accessing an array index, if you expect the array to contain null values.

    ColdFusion functions without return value (void)

    Not returning any value in a function, yet assigning the return to a variable will also turn out undefined.

    function foo() { return; }
    
    x = foo();
    writeDump(x);
    >> Exception: Variable X is undefined.
    

    yet

    x = { key: foo() };
    writeDump(x);
    

    cfdump void

    #JustColdFusionThings

    It's worth noting that newer versions of ColdFusion (and Lucee in general) support native null values. It still feels clunky working with them.