Creating A Struct From A ColdFusion Array Using The TreeMap And The LinkedHashMap

The other day, I was reading about Railo's ability to treat arrays as if they were structs. Specifically, Railo can execute a collection loop on an array in order to iterate over its defined indicies in order. In ColdFusion, we can of course use a FOR loop or an array loop to accomplish such things; but, there is something very nice about being able to loop over nothing but the defined indicies of an array. I wanted to see if I could mimic this behavior in Adobe ColdFusion by converting an array into a struct.

Arrays are ordered; structs are not. As such, we can't simply convert an array to a name-value collection. Doing so would be fine during insertion; but it would become unpredictable during iteration. In order to maintain iteration order for our array-as-struct representation, we need a way to maintain order within our collection.

Recently, Elliott Sprehn turned me onto a Java class called a TreeMap. The TreeMap implements the Map interface; but, it can iterate over the collection of keys using a composed comparator. By default, the comparator sorts the keys by alphabetical order. If we can find a way to make sure that our array indicies are alphabetically "correct," we should be able to use a TreeMap in order to create a collection-based array.

<cffunction

name="arrayCollection"

access="public"

returntype="struct"

output="false"

hint="I return a the given array as a collection of array keys in natural order. In order to maintain proper numeric ordering, the keys are zero-padded to all be the same length.">

As you can see in this demo, we are using a collection loop to iterate over an array. But, we aren't iterating over the array directly; rather, we're passing the array to our arrayCollection() user-defined function (UDF), which converts our array into a TreeMap. When we run the above code, we get the following output:

This iterates over the collection in index-order, using only the indicies that reference defined values.

This works, but it's not quite awesome. Because the default comparator of the TreeMap uses alphabetical comparisons, we have to zero-pad our index keys. If we don't do this, then alphabetically, "10" comes right after "1." Sure, we could have dropped down into the Java layer to build a numeric comparator; but, that would just have made the solution all the more complex.

Typically with a struct, we don't have control over the order in which are keys get assigned. In this case, however, since the conversion from array to struct is encapsulated within our UDF, insertion order is something that we do have control over. This scenario allows us to use a different kind of "ordered struct" - the LinkedHashMap.

The LinkedHashMap is another implementation of the Map interface. Unlike the TreeMap, however, the LinkedHashMap doesn't use key comparators. Rather, the LinkedHashMap iterates over the keys of the collection in insertion-order. That is, it returns the keys in the same order in which they were defined. This allows us to form the same kind of conversion with less code and friendlier keys:

<cffunction

name="arrayCollection"

access="public"

returntype="struct"

output="false"

hint="I return a the given array as a collection of array keys in insertion order.">

As you can see, this code was much more straightforward - no zero-padding, no key manipulation. We're simply adding the keys in an order reflective of the given array. And, when we run this code, we get the following output:

It's not often that I ever care about the order in which the keys of a struct are returned. And, it's even less often that I have arrays with undefined values. But, in the double-rare situation in which both those cases are true, we can dip down into the Java layer and use the TreeMap or the LinkedHashMap to help convert arrays to structs that can be treated as ordered collections.

Good point about the Java-based maps and case-sensitivity; I haven't tested that myself, but I believe I have seen that mentioned on some other blog. Luckily, in this case, numbers don't have a "case".

Funky re: posting. I am not sure why a different browser would work. Probably blog just hiccuped for a second (I only use FireFox to interact with my blog).

in Railo more and more since it not only saves me some typing, but I can always use <cfloop collection> and I don't have to worry what is it again? index or item? does it contain the key or the element? Anyway, the double benefit that only existing keys are respected makes it even more valuable for me.And I must admit that I always disliked the fact that I have to pass in arrayLen(somearray) to my to="" attribute. I hate it when I have to evaluate additional functions just to get the end of a loop. I even saved the array lenght in a variable before the loop and then looped over up to this variable, just that I don't have to execute the arrayLen() function. The only downside of your approach is that ACF uses reflection (just as Railo 3.x would do) in order to do all the Java calls. Which is quite time consuming. But I am sure you have some execution times there as well.

I definitely like the fact that you don't have to know what kind of item you're actually dealing with struct or array. It's like a generic each iterator that just worries about a given interface, not an actual data type.

I wrote my own version of your arrayCollection() functions using TreeMaps and LinkedHashMaps without reading yours based on your sample output. Turns out we think alike!

Main differences are you used arrayIsDefined() where I used isNull() with Array.get() and I favoured cfscript.

The TreeMap version is heaps less efficient with all of the key padding. Since we're talking about looping over arrays, the index will always be sorted, so a LinkedHashMap makes a lot more sense. It also seems a bit hacky asking for #women[01]# to get the first element.

My mistake. I was trying to delete an element of the java.util.LinkedHashMap in a for loop (decrement). Its possible, in other languages, to delete properties of an object that way but not in coldfusion apparently...

Anyway, thank you for your website, great ressources for a coldfusion noob like me ;)

I am the co-founder and lead engineer at InVision App, Inc — the world's leading prototyping,
collaboration & workflow platform. I also rock out in JavaScript and ColdFusion 24x7 and I dream about
promise resolving asynchronously.