Way back when ColdFusion 4.5 was released, the concept of structures (associative arrays to some of you) was introduced. Never one to be receptive to change - not to mention having no background in other programming languages - I shunned structures for the most part and kept on my merry way working with arrays and lists. Over the years, however, I have come to appreciate the simplicity and functionality of structures and embrace them as my favorite ColdFusion data type.
If you haven't worked with other languages such as the various flavors of C or Java, the concept of what structures are may be hard to grasp - so I hope to make it easier for you.
Like arrays and query results, structures are considered to be complex data types - as opposed to numbers or strings. Structures are made up of key/value pairs logically grouped together. For instance, the following structure contains information related to a contact:
Contact.FirstName = "Selene"
Contact.LastName = "Bainum"
Contact.EmailAddress = "selene@webtricks.com"
ColdFusion has many built-in structures that you have worked with, but may not realize: Server, Application, Session, Request, Form, URL, and Variables. So the Session structure is a logical grouping of all session information, the Application structure is a logical grouping of all application information and so on.
Structure Notations
There are two ways to access the values of structure keys: dot notation and indexed notation. You're already familiar with dot notation: Form.FieldName, Session.SessionID, Application.DSN, etc. These are all examples of structures and keys. FieldName is a key of the Form structure, SessionID is a key of the Session structure, and DSN is a key of the Application structure.
Dot notation is easy and familiar, but it has several limitations. First, the key name must be a syntactically correct ColdFusion variable name - you probably already know your form field names can't start with a number or contain spaces. Second, the name of the key must be a static value. If it's not a static value, you must use the Evaluate() function, which can be resource expensive.
Indexed notation is much more flexible: key names can start with a number and/or contain spaces and variables can be used to represent all or part of the key name without needing to use Evaluate(). With indexed notation, the key name is placed between brackets, with the left bracket being immediately to the right of the structure name. If the key is a numeric value, quotes are optional around the key name, but quotes are required for a string key name. If there are no quotes around a string key, ColdFusion will assume the value is the name of a variable and will attempt to retrieve the value of that variable to use as the key name.
For example:
Form[1] // = Form["1"]
Session["SessionID"]
varA = "DSN"
varB = 1
Request[varA] // = Request["DSN"]
Request["DSN" & varB] or Request["DSN#varB#"] // = Request["DSN1"]
Looking at indexed notations, you may notice some similarities between structures and arrays. They are, with the following notable differences:
<cfset Cart = StructNew() />
<cfparam name="Cart.BillingStruct" default="#StructNew()#" />
In the example above, a new empty structure called Cart will be created. Notice that StructNew() uses no arguments. If the structure already existed, it will be overwritten with the empty structure. Using cfparam, however, the new empty structure of Cart.BillingStruct will only be created if it doesn't already exist. Both methods are very useful, depending on your desired results.
If you've worked with any of the built-in ColdFusion data types, you have already set and modified the key/value pairs of structures. Using dot or indexed notation, you can easily set and update values of structure keys:
<cfset Application.DSN = "mydb" />
<cfset Cart["CCNum"] = "xxxx" />
<cfparam name="Cart.ExpDate" default="12/06" />
In the examples above, the structure will be created if it doesn't already exist, the key will be added if it isn't there already and the value of the key will be set. If the key already existed, its value will be overwritten with the new value.
Working with Structures
There are many structure functions that are quite useful when working with structures.
As you already know, ColdFusion has many decision functions, and several work with just structures. The IsStruct() function can be used to determine if an object is a structure or not:
<cfscript>
Cart = StructNew();
BillingFirstName = "Selene";
Test1 = IsStruct(Cart); // returns "yes"
Test2 = IsStruct(BillingFirstName); // returns "no"
</cfscript>
The StructIsEmpty() function can be used to determine whether or not a structure contains any keys:
<cfscript>
Cart = StructNew();
BillingStruct.FirstName = "Selene";
ShippingFirstName = "Dave";
Test1 = StructIsEmpty(Cart); // returns "yes"
Test2 = StructIsEmpty(BillingStruct); // returns "no"
Test3 = StructIsEmpty(ShippingFirstName); // error
</cfscript>
The values of Test1 and Test2 aren't surprising. However, setting the value of Test3 will return an error because the object being passed to the function isn't a structure.
You're probably very familiar with the IsDefined() function to test for the existence of an object in ColdFusion. The variable passed in can be of any data type, so the function is extremely useful. There's also a function called StructKeyExists() that's used only to test the existence of a key in a particular structure:
<cfscript>
Cart = StructNew();
BillingStruct.FirstName = "Selene";
ShippingFirstName = "Dave";
Test1 = StructKeyExists(Cart, "CCNum"); // returns "no"
Test2 = StructKeyExists(BillingStruct, "FirstName"); // returns "yes"
Test3 = StructIsEmpty(ShippingFirstName); // error
</cfscript>
Again, the setting of Test3 will return an error because ShippingFirstName isn't a structure.
By Reference Versus by Value
Objects and variables
are passed by one of two ways: reference or value. When an object is
passed by value, a new object is created that has the same value as the
original. When an object is passed by reference, the new object is
simply a pointer to the value of the original object stored in memory.
Most of us are used to variables that are passed by value. For example:
<cfset BillingFirstName = "Selene" />
<cfset ShippingFirstName = BillingFirstName />
<cfset BillingFirstName = "Dave" />
<cfoutput>#ShippingFirstName# - #BillingFirstName#</cfoutput>
The output of this code would be "Selene - Dave" because the value of BillingFirstName - in this case "Selene" - is passed to ShippingFirstName as its value. Even after BillingFirstName is changed to "Dave" the value of ShippingFirstName doesn't change.
The same is not true for objects that are passed by reference. If the example above was passed by reference as opposed to value, the output would be "Dave - Dave" because the value of BillingFirstName wouldn't be copied to the value of ShippingFirstName, rather a reference (or pointer) to BillingFirstName would be saved to ShippingFirstName. This means that when the value of BillingFirstName changes, the value of ShippingFirstName is also changed because ShippingFirstName just points to the value of BillingFirstName.
A good example of this is the billing and shipping information stored for an online purchase. Typically, the purchaser fills out his billing information and the next page contains the same information pre-populated in the shipping form. The purchaser can modify any information and then proceed with the checkout process. The following code would take the information entered into the billing form and then copy it to the shipping structure:
<cfscript>
Session.BillingStruct = StructNew();
Session.BillingStruct.FirstName = Form.FirstName;
Session.BillingStruct.LastName = FormLastName;
Session.ShippingStruct = Session.BillingStruct;
</cfscript>
On the shipping information page, the form is populated with the information in the shipping structure, which is a reference to the billing structure. When the shipping information is posted, the structure can be updated:
<cfscript>
Session.ShippingStruct.FirstName = Form.FirstName;
Session.ShippingStruct.LastName = FormLastName;
</cfscript>
If the purchaser changed no information, there's no problem.
However, any changes made will actually be made to Session.BillingStruct since Session.ShippingStruct is just a pointer. That means the billing information may no longer be correct.
To avoid this potential pitfall, the StructCopy() function can be used:
<cfscript>
Session.BillingStruct = StructNew();
Session.BillingStruct.FirstName = Form.FirstName;
Session.BillingStruct.LastName = FormLastName;
Session.ShippingStruct = StructCopy(Session.BillingStruct);
</cfscript>
When StructCopy() is used, the new structure will no longer be a pointer to the existing one, rather it will be a duplicate of the original structure. If a change is now made to Session.ShippingStruct, the values in Session.BillingStruct won't be affected and vice-versa.
Looping Over a Structure
There are two easy ways to loop over structures in ColdFusion: using the cfloop tag and a for loop in cfscript:
<cfscript>
BillingStruct = StructNew();
BillingStruct.FirstName = "Selene";
BillingStruct.LastName = "Bainum";
for(key in BillingStruct) {
writeOutput(key & " = " & BillingStruct [key] & "<br />");
}
</cfscript>
<cfloop collection="#BillingStruct#" item="key">
<cfoutput>#key# = #BillingStruct[key]#<br /></cfoutput>
</cfloop>
Both the for loop and the cfloop examples above will return exactly the same results. The for loop method is great if you're only performing actions in the loop that can be done in cfscript. The following example will copy all of the fields in a form post to another structure:
<cfparam name="Cart.BillingStruct" default="#StructNew()#" />
<cfscript>
for(field in Form) {
Cart.BillingStruct[field] = Form[field];
}
</cfscript>
Why wouldn't you just set Cart.BillingStruct equal to StructCopy(Form) you ask? If Cart.BillingStruct already existed and had keys, the StructCopy() function would overwrite the existing structure, whereas looping over the form and setting each field individually retains any previously existing Cart.BillingStruct keys that may not exist in the Form structure.
Combining Arrays and Structures
Structures can be
extremely useful when used in combination with other structures and
arrays. While structures can only be one dimension, the value of a key
can be an array or even another structure, thus appearing to be
multi-dimensional.
Throughout this article I've referred to a Cart structure that would hold the information from a shopping cart. Listing 1 provides a peek at a fuller version of this structure.
The first part of this example just sets form variables that would be similar to those passed by a shopping cart. The second part of the example creates a complex structure of structures and arrays of structures to hold all the information. What can you do with this structure? Pass it to a function!
Structures with Components and Functions
One of
the most useful applications for structures that I've found is using
them in conjunction with ColdFusion Components (cfcs). One of the
drawbacks with a component is declaring every possible argument that
may be passed to a function. If you're creating a function to insert a
record with 20 columns, you may have to declare 20 arguments. However,
if you pass a structure of those 20 elements into the function, you
only have to declare one argument - the structure! This is also very
helpful when it comes to adding and removing values - you don't have to
update your argument declarations and your function calls - you only
have to update the elements in the structure. The drawback, however, is
that you can't force a structure element to be required or of a certain
type without additional logic - validation that declaring the arguments
of a function lets you do quite easily.
Using the shopping cart example above, it would be very difficult - not to mention annoying - to pass all the individual arguments into a function. Instead, the example in Listing 1 shows how to call the function passing in the structure and how the function itself would process the structure:
While not a complete shopping cart processing example, you can see how you can take a complex structure and process it by accessing particular elements and looping over others dynamically. Structures like this greatly clean up as well as modularize your applications.
While it would be impossible to list all structure uses and functions in a single article, you should now know what you need to start using structures to help make your applications more modular and efficient.