Learning ColdFusion 8: Implicit Struct And Array Creation
One of the coolest features of ColdFusion 8 is its new implicit struct and array creation. For those of you who love Javascript, you will be used to seeing this type of struct definition:
<cfset objData = {
Key1 = "Value1",
Key2 = "Value2"
} />
... and this type of array definition:
<cfset arrData = [
"Value1",
"Value2"
] />
Prior to ColdFusion 8, you have to do the above definitions with a StructNew() (or ArrayNew( 1 )) and several CFSet tags. Clearly, this implicit creation is going to really whip the llama's ass... or is it?
Before I could really get to the bottom of how hard this was going to rock, I need to figure out how it worked, where it worked, and more importantly, where it did NOT work. First, let's test just the very basic implicit struct and array creation as we did above:
<!---
Create the date plan using ColdFusion 8's
new implicit struct creation.
--->
<cfset objDate = {
Pickup = "7:45 PM",
Dinner = "Outback Steak House",
Dessert = "Cafe Lalo"
} />
<!--- Dump out the data to see if it worked. --->
<cfdump
var="#objDate#"
label="Implicit Struct Creation Test"
/>
<!---
Create an array of movie times using
ColdFusion 8's new implicit array creation.
--->
<cfset arrMovieTimes = [
"7:00 PM",
"9:15 PM",
"11:14 PM"
] />
<!--- Dump out the data to see if it worked. --->
<cfdump
var="#arrMovieTimes#"
label="Implicit Array Creation Test"
/>
Running the above code, we get the following CFDump outputs:
That worked quite nicely. But, let's face it, that's really basic. Let's start pushing the envelop a little bit. Let's try nesting our implicit struct and array construction. The ColdFusion 8 "What's New" documentation already says that this cannot be done, but I figured I would see that for myself:
<!---
Now, let's recreate the date, except this time, we
are going to combine the implicit struct creation
with the implicit array creation.
--->
<cfset objDate = {
Pickup = "7:45 PM",
Dinner = "Outback Steak House",
Movie = "28 Weeks Later",
MovieTimes = [
"7:00 PM",
"9:15 PM",
"11:14 PM"
],
Dessert = "Cafe Lalo"
} />
As expected, this throws the ColdFusion parsing error:
Invalid CFML construct found on line 66 at column 18. ColdFusion was looking at the following text: {
Ok, that doesn't work. But, maybe it doesn't work because it was using a nested array construction; maybe it will work if we use an implicit struct creation instead:
<!---
Now, let's recreate the date, except this time, we
are going to combine the implicit struct creation
with the implicit array creation.
--->
<cfset objDate = {
Pickup = "7:45 PM",
Dinner = "Outback Steak House",
Movie = "28 Weeks Later",
MovieTimes = {
Time1 = "7:00 PM",
Time2 = "9:15 PM",
Time3 = "11:14 PM"
},
Dessert = "Cafe Lalo"
} />
Nope. That gives us the same ColdFusion parsing error.
The beauty of a ColdFusion struct is that you can keep pumping key-value pairs into it and it will just overwrite any existing values. What happens if we do that same thing with implicit struct creation? In this next test, we are going to set the same key twice to see how it handles it:
<!---
Let's recreate the date plan using ColdFusion 8's
new implicit struct creation, but this time, we
are going to define one of the keys twice to see
how it reacts.
--->
<cfset objDate = {
Pickup = "7:45 PM",
Dinner = "Outback Steak House",
Dessert = "Cafe Lalo",
Dinner = "Ruby Fu's"
} />
<!--- Dump out the data to see if it worked. --->
<cfdump
var="#objDate#"
label="Implicit Struct Creation Test"
/>
Running the above code, we get the following CFDump output:
As you can see, the value "Ruby Fu's" properly overwrote the original Dinner value of "Outback Steak House." Nicely done.
Now that we know implicit struct and array creation works on some basic level, let's see if we can evaluate strings that contain implicit struct and array creation. You never know when something like this might be useful:
<!---
We know that we can use the implicit struct
creation directly, but can we evaluate it as
if it were a variable? Let's create a string
that will represent our implicit struct.
--->
<cfset strStructure = "{ Name = 'Must Love Dogs', Time = '7:30 PM' }" />
<!---
Now, let's try to evaluate the implicit structure
creationg and save it in to the movie variable.
--->
<cfset objMovie = Evaluate( strStructure ) />
<!--- Dump out the data to see if it worked. --->
<cfdump
var="#objDate#"
label="Implicit Struct Evaluation Test"
/>
When we try to run that code, we get the following ColdFusion error:
Invalid CFML construct found on line 1 at column 1. ColdFusion was looking at the following text: {
Ok, so no evaluation abilities.
Above, we discovered that we cannot have nested implicit struct creation, but the documentation says we can have nested structs - just not through implicit creation. Well, what happens if the nested structure is created through the use of a ColdFusion function? Remember our old-school function, StructCreate():
<cffunction
name="StructCreate"
access="public"
returntype="struct"
output="false"
hint="Creates a struct based on the argument pairs.">
<!--- Define the local scope. --->
<cfset var LOCAL = StructNew() />
<!--- Create the target struct. --->
<cfset LOCAL.Struct = StructNew() />
<!--- Loop over the arguments. --->
<cfloop
item="LOCAL.Key"
collection="#ARGUMENTS#">
<!--- Set the struct pair values. --->
<cfset LOCAL.Struct[ LOCAL.Key ] = ARGUMENTS[ LOCAL.Key ] />
</cfloop>
<!--- Return the resultant struct. --->
<cfreturn LOCAL.Struct />
</cffunction>
This ColdFusion user defined function (UDF) gives us the ability to create structures and give them initial values on the fly (this is essentially what implicit creation is doing). Let's see what happens if we use that function inside of an implicit struct creation:
<!---
As the documentation states for this beta, we cannot
nest implicit structs / arrays without an intermediary
variable. However, what happens if we use an intermediar
function call to create our structure? Let's try this
again with a combination of ColdFusion 8's new implicit
struct creation and our old-school StructCreate() UDF.
--->
<cfset objDate = {
Pickup = "7:45 PM",
Dinner = "Outback Steak House",
Movie = StructCreate(
Name = "28 Weeks Later",
Time = "7:45 PM"
),
Dessert = "Cafe Lalo"
} />
<!--- Dump out the data to see if it worked. --->
<cfdump
var="#objDate#"
label="Implicit Struct Creation / UDF Test"
/>
Notice that the StructCreate() is being set as the value to a key in the implicit struct. Running the above code, we get the following CFDump output:
That worked quite nicely. And, it got me thinking; this StructCreate() method, while useful, might be improved with ColdFusion 8's implicit struct creation. Think about it - in this scenario, what are we really doing? The goal of StructCreate() UDF in this case is just to echo back the struct to overcome the limitations of implicit creation. Since we know we can create implicit arrays and structures on the fly, why not just create an Echo() UDF to act as the intermediary variable?
<cffunction
name="Echo"
access="public"
returntype="any"
output="false"
hint="Just returns the first argument passed to it.">
<cfreturn ARGUMENTS[ 1 ] />
</cffunction>
Notice that this UDF just returns back the first argument passed to it. What we are going to do is just use this as a wrapper to shield the parser from nested implicit variable creation:
<!---
Now that we know that function calls can exist within
the nest of ColdFusion 8's new implicit struct creation,
we can use the Echo user defined function to really
create some intricate structures with minimal effort.
We are going to pass the implicit creation go the Echo
function and have it just bounce back.
--->
<cfset objDate = {
Pickup = "7:45 PM",
Dinner = "Outback Steak House",
Movie = Echo({
Name = "28 Weeks Later",
Time = "7:45 PM"
}),
Dessert = "Cafe Lalo"
} />
<!--- Dump out the data to see if it worked. --->
<cfdump
var="#objDate#"
label="Implicit Struct Creation w/ Echo Test"
/>
Notice that the Movie value is set to the return value of the Echo() UDF and to the Echo() UDF, we are passing an implicit struct. Since this worked with StructCreate(), it follows that it should work with this test. Unfortunately, this throws the following ColdFusion error:
Invalid CFML construct found on line 172 at column 25. ColdFusion was looking at the following text: {
As it turns out, you cannot pass an explicit struct or an array to a method call. You cannot even pass it if you give it a key:
<cfset objStruct = Echo(
Value = { Foo = "Bar" }
) />
I have to be honest here - this is very uncool. One of the most powerful features of implicit struct and array creation in other languages such as Javascript is the ability to, on the fly, create structures and pass them to functions. I am very disappointed that this does not work, and I think it really takes away from the usefulness of this feature. Hopefully this is just bug that will be fixed in the actual ColdFusion 8 product release.
So maybe you cannot pass implicit structs to a function, but can you at least pass them to attribute values:
<cfdump
var="#{ Movie = 'Mixed Nuts', Time = '7:45 PM' }#"
label="Implicit Attribute Test"
/>
Running the above code throws the following ColdFusion error:
Invalid CFML construct found on line 233 at column 15. ColdFusion was looking at the following text: { The CFML compiler was processing The tag attribute var. A cfdump tag beginning on line 232, column 2.
Ugggg! Are you telling me I cannot even do that? Well wait, ColdFusion 8 introduced a new attribute - AttributeCollection - which acts in place of the rest of the tag's attributes (in an either-or fashion). Maybe I cannot use implicit struct creation with a standard attribute, but surely I can use it with the AttributeCollection attribute:
<cfdump
attributecollection = "#{ var = 'Collection Test' }#"
/>
Nope. This throws the same ColdFusion error. This is really disappointing. I mean, you can't use implicit creation with method calls and you can't use it with attribute values. This is starting to look much less like on-the-fly functionality.
The documentation states that we cannot store an implicit struct into an array index, but can we store it into an struct key:
<!---
Now that we know that ColdFusion 8's new implicit
struct creation works on a basic level, let's try
to use it to set values into existing structs.
--->
<cfset objDate = {} />
<cfset objDate.Pickup = "7:45 PM" />
<cfset objDate.Dinner = "Outback Steak House" />
<cfset objDate.Movie = {
Name = "28 Weeks Later",
Time = "7:45 PM"
} />
<cfset objDate.Dessert = "Cafe Lalo" />
<!--- Dump out the data to see if it worked. --->
<cfdump
var="#objDate#"
label="Implicit Struct Creation w/ Struct Set"
/>
Running the above code, we get the following CFDump output:
Ok so at least that works nicely. Now, the ColdFusion 8 What's New documentation (page 12) states that you "cannot assign an implicitly created structure to an array element," but I figured I would give it a shot just to see it fail for my own eyes:
<!---
Even though the What's New In ColdFUsion 8 documentation
says, that this is not possible, let's try to store an
implicit struct into an array index.
--->
<cfset arrData = [] />
<cfset arrData[ 1 ] = { Movie = "Mixed Nuts", Time = "7:45 PM" } />
<cfdump
var="#arrData#"
label="Implicit Struct Creation w/ Array Set"
/>
Running the above code, we get the following CFDump output:
Apparently this does work. This may just be a case of the documentation not being updated since earlier betas or something (this is my first playing around with CF8).
So, I have to say, ColdFusion 8's new implicit struct and array creation is very cool; however, after some deep evaluation here, I am thinking that it is not as orgasmic as I was hoping it would be. I think, no question, implicit struct and array creation should be compatible with method calls and attribute values - hands down, not up for discussion. If you look at how these features are used in other languages, this is the majority of use cases. Creating structs without having to use StructNew() is nice, but honestly, that is such a small part of how something like this should be used.
I want to give a HUGE thanks to HostMySite.com for making ColdFusion8 beta testing accounts available. That is just awesome and perfect for those of us who have bosses that don't think installing CF8 Beta is a priority (grumble grumble grumble).
Want to use code from this post? Check out the license.
Reader Comments
Great analysis!
These were just the tests I was hoping to run for myself soon. I have to agree with you that it would have been nice to have nesting for inline array and structure creation.
More significantly, the limitation on passing it into a method call is significant. I was hoping to limit creation of named variables for temporary use (that have to be var'ed).
This definitely takes the feature down from "One of the best new features" to "A handy feature".
Ben,
If I were you I would file a bug report on this and send them a link to this page. Great work!
@Steve,
Thanks. I am with you on that - in that it just is not as awesome as it could be. Handy is the right word.
@Tony,
Will do that right now.
I had created a forum post for these exact same issues. Would you mind adding the newer stuff (like not being able to pass literals as arguments) to the thread so that they see it's not just me?
http://www.adobe.com/cfusion/webforums/forum/messageview.cfm?catid=648&threadid=1272237&forumid=72
@Rick,
Consider it done. I should really take a look at those forums; I have never really done anything with the Adobe forum before.
@Ben,
I think the "cannot assign an implicitly created structure to an array element" is intended to mean you can't do:
myArray = [
{some='thing', more='thing'},
{some='thing', more='thing'}
]
I personally was really excited when I first heard they were adding this feature. Maybe it's because of years doing heavy JS, but I find implicit declaration very clear and you can create some very large data sets, with pretty minimal code.
Also, as for you dynamic string evaluation sample, one thing to remember is you *can* convert a JSON string to CF natively. This would allow you to do something like:
<cfsavecontent variable="code">
[
{some: 'thing', more: 'thing'},
{some: 'thing', more: 'thing'}
]
</cfsavecontent>
<cfset myArray = deserializeJSON(code) />
NOTE: The above code is only for recreational use--please don't try to build code based on that sample. :)
Finally, I would really like to see them do the implicit declaration correctly. I'd rather not have it at all, then to have a poor implementation of it...
@Dan,
I have to look into this deserializeJSON() method. I have not used it before. I saw in the What's New documentation that they updated it (which I assume means it was part of CFMX 7???). It looks very cool, I will have to check it out.
As for the storing implicit things into an array, I think the documentation is just outdated. They are very specific in their sample code for this matter.
now we will look what is it. Thank you
Nice run through, "glad" to see that it isn't a good feature afterall.
Basically what you went over means that the new feature to create Objects/Arrays literals is pointless as you can't do anything that you should be able to do with them. This was something that I was most most looking forward to aswell as the addition of proper operators like: ! && || < <= >= + == etc.
Additionally something else that has not been addressed is the fact that creating a Struct in this format: object = {var1 = "string", var2 = 1} is incorrect - as far as other languages are concerned anyway. It should be like this: object = {var1 : "string", var2 : 1} notice the colons instead of equal signs?
Any insight on if that is going to be rectified?
@Shuns,
Scoprio is still in Beta, so who knows what the final product will hold. I suspect that this is just about what is going to be delivered as this Beta is now public. But, I cannot really speak to this end as I have not been involved in the CF development process.
Nice write up. Nested structures and arrays would be nice.
But doing something like this:
* <cfdump
* attributecollection = "#{ var = 'Collection Test' }#"
* />
would actually be more code not less so I don't really see the point in that.
@Sam,
I was testing it with the CFDump tag, but you are right - why would ever pass an implicit object to a CFDump tag. I was thinking more for use with other tags, but if it doesn't work in CFDump, I figured it wouldn't work with any other tags.
@Ben -- Oh yeah, I thought you meant for any tag. In JavaScript it makes a lot of sense to pass in arguments/attributes/call them what you want in that style, it just does not for cf.
@Sam,
I agree; I cannot think of a very good use case of passing in an implicit struct to a tag, but it just feels like something that should work.
Is it "attributescollection"? I thought that was an pre-exising input to custom tags. I thought the new one was "argumentscollection". At least, that's what I wrote down in my note's from Ben Forta's presentation to my user group.
@Brad,
It is AttributeCollection (singular). The Whats New documentation states that it was names ArgumentsCollection in earlier beta / alpha, but this is no longer.
Yes we cannot have dynamic array of structs either. So this does not work
<cfset pages=[{name="Carrot",value=2},{name="Rabbit",value=3}>
but you can have an array of lists using new style array creation:
<cfset pages=["carrot,2","Rabbit,3"]>
I then wrote a function to convert an array of Lists to an array of Structs (based on cflib.StructOfListsToArrayOfStructs).
<pre>
function ListsToStructs(items){
var fieldCount = 0;
var fieldname = '';
var fieldnames = ['name','value'];
var itemCount = 0;
var itemIndex = 0;
var params = {};
var result = [];
var values = [];
var valueCount = 0;
var valueIndex = 0;
var valueSeparator = ',';
if(arrayLen(arguments) GT 1) fieldnames = listToArray(arguments[2] );
if(arrayLen(arguments) GT 2) valueSeparator = arguments[3];
fieldCount = ArrayLen(fieldnames);
itemCount = ArrayLen(items);
result = [];
for ( itemIndex=1; itemIndex LTE itemCount; itemIndex = itemIndex + 1)
{
values = ListToArray( items[ itemIndex ], valueSeparator );
valueCount = min( ArrayLen( values ), fieldCount );
params = {};
for ( valueIndex = 1; valueIndex LTE valueCount; valueIndex = valueIndex + 1 )
{
fieldname = fieldnames[ valueIndex ];
params[ fieldname ] = values[ valueIndex ];
}
arrayAppend( result, params );
}
return result;
}
</pre>
Hi Ben,
Thanks for the great analysis... These inputs have been very useful.
Meanwhile, you can also spend some time playing with the cfpdf/pdfforms tags.
Regards,
-ahamad,
Adobe CF Team.
@Ahamad,
Thanks. I am making my way through the new features. I am sure I will get to CFPDF soon :)
@William,
I like that UDF. Thanks for posting it up here for us to see.
Just to add to the problems of implicit variable decorations:
Using any operator as an implicit struct key throws an error.
So
<cfset myStruct = { And = "break" } />
Doesn't work. Throws error... as do OR, MOD, EQ, LT, etc...
Where as:
<cfset myStruct = StructNew() />
<cfset myStruct.And = "okay" />
is fine.
Adds a lot of validation to creating dynamic structures using this method.
I tried adding quotes to the Keys and that doesn't work either.
@Brett,
Very interesting. I didn't even think to check anything like that. Good call.
Thanks. I am making my way through the new features. I am sure I will get to CFPDF soon :)
Education.com provides expert advice, features, columns, thousands of reference articles, and a community for parents of pre-school to high school students.
This sounds like a "too little too late" feature. CF implements features that other languages have had for decades, and still manage to botch it completely to the point of uselessness.
Frankly, your "CreateStruct" function is superior to the { } syntax in every way, other than being 10 characters more to type.
With your "CreateStruct" basically ALL of your tests would work, which they fail to do with the CF lame-ass "implicit creation".
CS(name='per', job = CS(title='programmer', salary=75000)) would work perfectly fine, while the CF-thing fails to work.
In Python, to take one random example, all of your tests would also work. Same for Javascript, Perl, Ruby, PHP, basically any scripting-language you care to compare with.
Thank you for saving me a few hours (or more!) trying to evaluate/create structs on the fly. I just hardwired my "answer", but figured that since I haven't really done anything of substance in CF since ver 5.0, I should investigate. Your run-through gave me all the answers & updates to know that "what's done is done!". I'm excited to explore Flex 3 ... wherein my future holds something along the lines of: 1) fetch data from DB
2) create object(s)
3) introspect the objects and map to a struct on the fly
Keep up the good work!
Mike
@Mike,
Glad to have added value to your day :)
This does not work either:
<cfset foo = somefunction({ firstname = "nick", lastname = "smith" })>
@Nick,
At least they are getting closer. With the ColdFusion 8 updater, the will *probably* be allowing nested array and struct creation. It's a step closer.
Hi,
Currently, nested struct/array creation is not supported for:
1. Passing implicit structures/arrays to functions calls.
This is NOT allowed
MyName = MyStruct({ First = "Adobe", Last = "ColdFusion"});
2. Returning implicit structures/arrays as values.
This is NOT allowed
<cfreturn { First = "Adobe", Last = "ColdFusion"}>
Also Nested Struct/Array creation is not supported as of CF8.0
Ahamad,
ADOBE CF Team
It sounds like the nested creation problem was fixed in the 8.0.1 update. It's mentioned under "What's new and changed in this release" in the release notes:
http://www.adobe.com/support/documentation/en/coldfusion/801/cf801releasenotes.pdf
ColdFusion now supports nesting structure and array creations by using syntax such as the following: a=[[1,2],[3,4],[5,6,7],{a=2}]
@Steven,
Oh heck yeah :) Me likey.
Hi,
With the CF8 updater 1(8.0.1), we support Implicit nested array/struct creation.
Here are a few simple examples.
<cfset array1 = [ ["10", "20"], ["30", "40"] ]>
<cfset struct1 = { str1 = {key1 = "10", key2 = "20"}, str2 = {key3 = "30", key4 = "40"} }>
I was frustrated with this same issue PLUS the way it uppercases the keys in a struct (I do a lot of remoting):
<cfset foo = {myCamelCaseKey="hello world"}/>
becomes:
foo.MYCAMELCASEKEY
So I went back to using my old standard:
<cffunction name="createStruct" access="public" returntype="struct">
<cfreturn arguments>
</cffunction>
Which IMHO is a great solution 'cause it respects case:
<cfset foo = createStruct( myCamelCaseKey = "hello" )/>
And you can use it to create pseudo-Arrays (Structs that behave like Arrays):
<cfset foo = createStruct( "One", "Two", "Three" )/>
<cfset ArrayAppend( foo, "Four" );
Thanks for the great post, just thought I would share...
i am working with coldfusion for design web site & data sources,
but i am a problem, please help me,
please tell me about this error in setings of data sources:
Unable to update the ColdFusion ODBC Server.
Timeout period expired without completion of C:\ColdFusion8\db\slserver54\admin\swcla.exe
With 8.01 I'm trying to create a nested structure based on postprocessing a query and dynamically creating the keys and the following works fine with a single level structure with objQry being a valid query. However when I try a nested structure by uncommenting the commented lines I get a 'Invalid CFML construct found' on the line where 'stItem[objQry.abiitmkey] = {'. Any ideas what I could do to fix this. I want to be able to reference
stItem[objQry.abiitmkey].stFY[objQry.fy].qty for instance when I am done.
---------
<cfif Not StructKeyExists(stItem, "#objQry.abiitmkey#")>
<cfscript>
stItem[objQry.abiitmkey] = {
abiitmkey = objQry.abiitmkey,
risk = objQry.risk,
/* stFY[objQry.fy] = { */
fy = 0,
qty = 0
/* } */
};
</cfscript>
</cfif>
--------
@BrianO,
I am not sure that you can use dynamic naming in an implicit struct notation, only dynamic values.
Maybe it supports nested arrays and structs, but this syntax is still not supported when using inline in a function call:
<cfset methodCall(["a","b"])>
returns "When using named parameters to a function, every parameter must have a name.".
If I give the argument a name in the call, I keep getting this error.
Indeed, as you say, very uncool.
@casey: I use your method a lot, but for arrays the order is not guaranteed just like that, which is what I need. Calling createStruct(a="x",b="y") does not always return x,y in that order. It does work if you call createStruct() without arguments and then put items in later. But then the benefit of an inline call is lost. But still, a very powerful feature to use the argumentcollection like that.
I was looking for a tutorial like this and thank you thank you thank you for sharing this great knowledge.
I have a tab delimited text file where I need to create an array where each array element is a structure.
Your example of using implicit struct make my code so easy and clean!
Thanks Ben!
I sort of fell into a gotcha with this cause I was writing a new app on CF9 Beta, and in a few lines of code I was passing implicit structures as function arguments. It all worked as expected, until I deployed to our CF8 staging server and started getting errors of course.
Glad to see they finally took care of that in the upcoming release!
@Jose,
Yeah, it's an update that has been long-time desired. I think minor updates like that are a huge part of what is making CF9 so awesome.
Love your blog. You always cover things in such detail. Thanks for the info about implicit struct creation. Would never have done it. We're on CF9 now but I would have reached for scructnew. Ciao.
@Zachary,
No problem - it is absolutely my pleasure. ColdFusion 9 actually upgraded the implicit struct / array usage beyond blog post. Check it out:
www.bennadel.com/blog/1641-Learning-ColdFusion-9-Implicit-Struct-And-Array-Usage.htm
Some of the nice improvements are being able to pass them directly to a function or tag attribute.
Works:
<cfset local.rv = [iif(arguments.in.elementid,1,0)]>
Does not Work:
<cfset local.rv = [iif(arguments.in.elementid!=0,1,0)]>
Conclusion:
coldfusion sucks...