ColdFusion in Context: Read ColdFusion Arrays

ColdFusion arrays are not spreadsheets or solid structures of (for example) X by Y by Z cells. They are not even nested lists. The best three-dimensional analogy for a ColdFusion array may be a bush consisting of many branches of varying lengths with holes in them and more branches. If you immmerse such a bush into liquid (representing server memory), it only displaces space for the cells you have defined.

The usual way of conceputalizing relationships is through a spreadsheet or cube arrangement. If you immerse a cube into liquid (analogous to server memory), it would displace X by Y by Z cells of volume, even though many of its cells aren't used.

From this description, it should be clear that ColdFusion arrays conserve memory. No space is wasted on undefined cells, not even pointers to them. Holes in a ColdFusion array can't be accessed; they don't exist. However, our programming constructs to access data usually expect data to be stored as if it were a spreadsheet or cube. When they encounter an undefined cell, they will die with an error. You could use the arrayset function to predefine many cells for a given dimension of an array in order to treat a large array as a big cube, but this would waste the space that ColdFusion is trying to conserve by default.

ColdFusion provides many functions, but only one won't throw an error when it tries to read an undefined array element: cfparam. (The isDefined function complains that the cell is not a "syntactically valid variable name" and errors out. The isArray function says the element "cannot be found." Other functions die for similar reasons.) However, cfparam fills the cell if it doesn't exist; this negates the storage advantages of using ColdFusion arrays to some extent. If you use cfparam to walk the array, you'll wind up filling at least a copy of some portion of the array in order to read it safely.

There is another way: you can use cftry and cfcatch blocks to ignore the error returned by tags that don't fill the cell when they try to read it. This tip uses this technique to safely walk through a ColdFusion array that has a branch that reaches the third dimension. (The general procedure can be extended for more dimensions.) Along the way, you'll work with index loops and use cftry and cfcatch blocks to override errors. Save this code as array.cfm.

Make an Array

To begin, you'll need a suitably complex ColdFusion array. Initially, it's declared as a one-dimensional array. The first element is empty. The second is left undefined. The third is another array... The fourth has as its second element an array whose second element is still another array... The point is that this array has branches to follow and holes for the unwary to fall into.

<cfset aOne=arrayNew(1)>
<cfset aOne[1]="">
<cfset aOne[3]=arrayNew(1)>
<cfset aOne[3][1]="apple">
<cfset aOne[3][3]="">
<cfset aOne[4]=arrayNew(1)>
<cfset aOne[4][2]=arrayNew(1)>
<cfset aOne[4][2][2]="">
<cfset aOne[4][2][30]="fruitsalad">
<cfset aOne[5]="grapefruit">

See if the Array is Really an Array

First, start by seeing if the array is really an array. Set an impossible value for its length and then try to overwrite it with the arraylen function. If this works, it's an array. If it doesn't, then the cftry and cfcatch tags will hide the error that would otherwise be thrown.

<cfset ilen=-1>
<cftry>
<cfset ilen=arrayLen(aOne)>
<cfcatch>
</cfcatch>
</cftry>

If the length is still that impossible value, then you might be dealing with a simple variable. If you can take its length, it's a simple variable; display it. If you can't, call it undefined. (This code doesn't handle structures.) If its length is zero, it's empty.

<cfif ilen lt 1>
  <cftry>
  <cfset ilen=len(aOne)>
  <cfcatch>
  </cfcatch>
  </cftry>
  <cfif ilen is 0>
    aOne is empty
  <cfelseif ilen is -1>
    aOne is not defined
  <cfelse>
    <cfoutput>aOne is #aOne#</cfoutput><br>
  </cfif>

Work with the First Dimension

If you made it this far, the arraylen function returned the length of the array's first dimension in "ilen". Now, loop over the array's possible elements and repeat the process used above to see if the element is itself an array, if it is a simple variable, or if it exists at all. This time, as you use the arraylen and len functions to find the length of the array or of the simple variable, store the length in "jlen".

<cfelse>
  <cfloop index=i from=1 to=#ilen#>
  <cfset jlen=-1>
  <cftry>
  <cfset jlen=#arrayLen(aOne[i])#>
  <cfcatch>
  </cfcatch>
  </cftry>
  <cfif jlen lt 1>
    <cftry>
    <cfset jlen=#len(aOne[i])#>
    <cfcatch>
    </cfcatch>
    </cftry>
    <cfif jlen is 0>
      <cfoutput>aOne[#i#] is empty</cfoutput><br>
    <cfelseif jlen is -1>
      <cfoutput>aOne[#i#] is not defined</cfoutput><br>
    <cfelse>
      <cfoutput>aOne[#i#] is #aOne[i]#</cfoutput><br>
    </cfif>

Work with the Second Dimension

If you made it this far, the arraylen function returned the length of the array's second dimension in "jlen". Now, loop over the array's possible elements and repeat the process used above to see if the element is itself an array, if it is a simple variable, or if it exists at all. This time, as you use the arraylen and len functions to find the length of the array or of the simple variable, store the length in "klen". (Does this start to sound familiar?)

  <cfelse>
    <cfloop index=j from=1 to=#jlen#>
    <cfset klen=-1>
    <cftry>
    <cfset klen=#arrayLen(aOne[i][j])#>
    <cfcatch>
    </cfcatch>
    </cftry>
    <cfif klen lt 1>
      <cftry>
      <cfset klen=#len(aOne[i][j])#>
      <cfcatch>
      </cfcatch>
      </cftry>
      <cfif klen is 0>
        <cfoutput>aOne[#i#][#j#] is empty</cfoutput><br>
      <cfelseif klen is -1>
        <cfoutput>aOne[#i#][#j#] is not defined</cfoutput><br>
      <cfelse>
        <cfoutput>aOne[#i#][#j#] is #aOne[i][j]#</cfoutput><br>
      </cfif>

Work with the Third Dimension

If you made it this far, the arraylen function returned the length of the array's second dimension in "klen". Now, loop over the array's possible elements and repeat the process used above. Store the length in "llen".

    <cfelse>
      <cfloop index=k from=1 to=#klen#>
      <cfset llen=-1>
      <cftry>
      <cfset llen=#arrayLen(aOne[i][j][k])#>
      <cfcatch>
      </cfcatch>
      </cftry>
      <cfif llen lt 1>
        <cftry>
        <cfset llen=#len(aOne[i][j][k])#>
        <cfcatch>
        </cfcatch>
        </cftry>
        <cfif llen is 0>
          <cfoutput>aOne[#i#][#j#][#k#] is empty</cfoutput><br>
        <cfelseif llen is -1>
          <cfoutput>aOne[#i#][#j#][#k#] is not defined</cfoutput><br>
        <cfelse>
          <cfoutput>aOne[#i#][#j#][#k#] is #aOne[i][j][k]#</cfoutput><br>
        </cfif>

Report More Dimensions and Close the Loops

If arraylen succeeded this time, there are more than three dimensions. Point out that this element is itself an array and give its length. Finally, close the conditions and loops.

      <cfelse>
        <cfoutput>aOne[#i#][#j#][#k#] is an array of length #llen#</cfoutput><br>
      </cfif>
      </cfloop>
    </cfif>
    </cfloop>
  </cfif>
  </cfloop>
</cfif>

Array Discussion

Try the code. Notice the long string of undefined cells the code walks through at one point. ColdFusion doesn't waste any space on these empty cells; other languages would. Change the values to see how this changes the output.

Look for other ways to accomplish the same goal. Perhaps you can create a recursive version of this code to handle any number of dimensions. Perhaps you can extend it to handle structures nested within arrays or arrays nested within structures. Perhaps you can find or build a tag that doesn't error out when it tries to read an array element that doesn't exist. Then show us what you've done. =Marty=