ColdFusion Puzzler - Inspect It!

This post is more than 2 years old.

Today's ColdFusion Puzzler is based on a cool Groovy feature. I was surprised to discover that Groovy supports a Dump function. While I don't find it as pretty as ColdFusion's version, it's nice to have when debugging. But Groovy takes it a bit further and adds something similar called the inspect() function. The inspect function will take any arbitrary object and return a string that could be used to create it. Here is an example:

def s = [ name:"Raymond", age:35, rank:"Jedi" ]

def a = [0,2,3] def b = new Date()

s.a = a s.bornondate = b

println s.inspect()

This returns:

["name":"Raymond", "age":35, "rank":"Jedi", "a":[0, 2, 3], "bornondate":Fri Sep 12 08:48:16 CDT 2008]

As you can see, it isn't the code I used but code that would generate the same data.

Your challege, should you choose to accept it, is to write a similar function for ColdFusion. Your output need not look the exact same of course. I've provided a simple example that only works with arrays to get your started.

<cfscript> function inspect(arr) { var r = ""; var i = "";

r = "[";

for(i=1; i &lt;= arrayLen(arr); i++) {
	r &= arr[i];
	if(i &lt; arrayLen(arr) ) r&=",";
}

r &= "]";
return r;

} </cfscript>

<cfset a = [1,2,9,20]> <cfoutput>#inspect(a)#</cfoutput>

Your code should handle arrays, structs, and simple values. For extra credit you can handle queries to by using a bunch of query set sells.

Also note that my test UDF returns a literal value like Groovy. You can also return a series of statements instead:

ob = arrayNew(1); ob[1] = 1; ob[2] = 2; etc

Note that I used "ob" to represent the top level data. Since I pass the variable, and not the variable name, I chose an arbitrary variable name to store the data.

Enjoy!

Raymond Camden's Picture

About Raymond Camden

Raymond is a senior developer evangelist for Adobe. He focuses on document services, JavaScript, and enterprise cat demos. If you like this article, please consider visiting my Amazon Wishlist or donating via PayPal to show your support. You can even buy me a coffee!

Lafayette, LA https://www.raymondcamden.com

Archived Comments

Comment 1 by Dave Ferguson posted on 9/12/2008 at 6:16 PM

This is probably cheating but hey why reinvent the wheel?

<CFSCRIPT>
a.testvar1 = 'this is part 1';
a.testvar2 = 'this is part 2';
a.testvar3 = [1,2,9,20];
writeoutput(inspect(a));
</CFSCRIPT>

<CFFUNCTION NAME="inspect" RETURNTYPE="STRING">
<CFARGUMENT NAME="item" REQUIRED="YES">
<CFSAVECONTENT VARIABLE="contRet">
<CFDUMP VAR="#arguments.item#" FORMAT="TEXT">
</CFSAVECONTENT>
<CFRETURN contRet>
</CFFUNCTION>

--Dave

Comment 2 by Raymond Camden posted on 9/12/2008 at 6:18 PM

I don't mind cheating (if it is good enough for Captain Kirk, it is good enough for me), but that doesn't solve the problem. The result should be a string that could be executed to create the same data.

Comment 3 by Ben Nadel posted on 9/12/2008 at 6:43 PM

How about:

<cffunction name="Inspect">
<cfreturn (
"DeserializeJSON(" &
SerializeJSON( ARGUMENTS[ 1 ] ) &
")"
) />
</cffunction>

Comment 4 by Elliott Sprehn posted on 9/12/2008 at 6:47 PM

@Ray

The inspect() method on objects in Groovy is actually stolen from ruby. :)

The fact that you can't create implicit queries and that implicit arrays and structs aren't allowed in expression contexts reduces the value of the CF kind of function quite a lot.

Instead you'd end up with tons of temporary variables. :/

Comment 5 by Raymond Camden posted on 9/12/2008 at 6:50 PM

@Ben - Nice. :)

@Elliott - Thanks for the info re: inspect. As for the usefulness - please remember - this is for fun, nothing more. ;)

Comment 6 by todd sharp posted on 9/12/2008 at 6:54 PM

Ben stole my idea.

Comment 7 by todd sharp posted on 9/12/2008 at 7:09 PM

<cffunction name="inspect">
<cfargument name="ob" required="true" type="any" />
<cfset var ret = "" />
<cfwddx action="cfml2wddx" input="#arguments.ob#" output="ret" />
<cfreturn toString(ret) />
</cffunction>

<cfset a = arrayNew(1) />

<cfset b = structNew() />
<cfset b.name = "todd" />
<cfset b.skillLevel = "superior" />

<cfset arrayAppend(a,b) />

<cfquery name="c" datasource="cfartgallery">
select *
from artists
</cfquery>

<cfset arrayAppend(a,c) />
<cfdump var="#a#">

<cfset i = inspect(a) />
<cfoutput>#i#</cfoutput>

Comment 8 by Ben Nadel posted on 9/12/2008 at 7:10 PM

@Todd,

:P ... I like the WDDX approach also.

Comment 9 by Raymond Camden posted on 9/12/2008 at 7:16 PM

@Todd - Your solution is as wrong as the first commenter. It needs to be something that can be executed or run via cfinclude.

Comment 10 by todd sharp posted on 9/12/2008 at 7:28 PM

OK so then what im i doing wrong then - I can't get Ben's solution to do what you're looking for either?

Comment 11 by Ben Nadel posted on 9/12/2008 at 7:36 PM

Whoa, whoa, don't pick on me :)

Comment 12 by todd sharp posted on 9/12/2008 at 7:54 PM

OK I'm sure I over complicated this (I'm medicated today) :) But this works --

<cffunction name="inspect">
<cfargument name="ob" required="true" type="any" />
<cfset var towddx = "" />
<cfset var ret = "" />

<cfwddx action="cfml2wddx" input="#arguments.ob#" output="towddx" />

<cfsavecontent variable="ret">
&lt;cfwddx action="wddx2cfml" input="<cfoutput>#towddx#</cfoutput>" output="cfml" /&gt;
&lt;cfdump var="#cfml#" /&gt;
</cfsavecontent>

<cfreturn replace(replace(ret, "&lt;", "<", "all"), "&gt;", ">", "all") />
</cffunction>

<cfset a = arrayNew(1) />

<cfset b = structNew() />
<cfset b.name = "todd" />
<cfset b.skillLevel = "superior" />

<cfset arrayAppend(a,b) />

<cfquery name="c" datasource="cfartgallery">
select *
from artists
</cfquery>

<cfset arrayAppend(a,c) />

<cfset i = inspect(a) />
<cfdump var="#i#">

<cfset tempfile = "#getDirectoryFromPath(getCurrentTemplatePath())#/inspect.cfm" />

<cffile action="write" file="#tempfile#" output="#i#" />

<cfinclude template="inspect.cfm" />

Comment 13 by Justice posted on 9/12/2008 at 8:18 PM

OK, I took a stab at it. Got it working for strings, arrays, and structures. Anyone wanna extend to queries? =)

<!---// Code //--->

<cfscript>
function inspect(obj) {
var result = { dataType = '', code = '' };
result.dataType = getDataType(obj);

switch (result.dataType) {
case 'array':
result.code = '<cfset theVar = {';
for (x = 1; x LTE arrayLen(obj); x++) {
result.code = result.code & obj[x];
if (x != arrayLen(obj)) {
result.code = result.code & ',';
}
}
result.code = result.code & '} />';
break;

case 'struct':
result.code = '<cfset theVar = { ';
structKeyArr = structKeyArray(obj);
for (x=1; x LTE arrayLen(structKeyArr); x++) {
result.code = result.code & structKeyArr[x] & '=' & obj[structKeyArr[x]];
if (x != arrayLen(structKeyArr)) {
result.code = result.code & ',';
}
}
result.code = result.code & '} />';
break;

case 'string':
result.code = '<cfset theVar = "' & obj & '" />';
break;
}

return result;
}

function getDataType(obj) {
var dataType = 'unknown';

if (isSimpleValue(obj)) {
dataType = 'string';
}
if (isArray(obj)) {
dataType = 'array';
}
if (isStruct(obj)) {
dataType = 'struct';
}
if (isQuery(obj)) {
dataType = 'query';
}

return dataType;
}

</cfscript>

<cfset theArray = [12,23,23,345,56] />
<cfset theStruct = { test="yes", age=45, message="hello" } />
<cfset theString = "Hello" />

<cfoutput>
String Result: <cfdump var="#inspect(theString)#"><br />
Array Result: <cfdump var="#inspect(theArray)#"><br />
Structure Result: <cfdump var="#inspect(theStruct)#"><br />
</cfoutput>

Comment 14 by Justice posted on 9/12/2008 at 8:20 PM

and I posted it too fast, revised code to fix missing quotes in the structure creation =)

<!---// Code //--->

<cfscript>
function inspect(obj) {
var result = { dataType = '', code = '' };
result.dataType = getDataType(obj);

switch (result.dataType) {
case 'array':
result.code = '<cfset theVar = {';
for (x = 1; x LTE arrayLen(obj); x++) {
result.code = result.code & obj[x];
if (x != arrayLen(obj)) {
result.code = result.code & ',';
}
}
result.code = result.code & '} />';
break;

case 'struct':
result.code = '<cfset theVar = { ';
structKeyArr = structKeyArray(obj);
for (x=1; x LTE arrayLen(structKeyArr); x++) {
result.code = result.code & structKeyArr[x] & '= "' & obj[structKeyArr[x]] & '"';
if (x != arrayLen(structKeyArr)) {
result.code = result.code & ',';
}
}
result.code = result.code & '} />';
break;

case 'string':
result.code = '<cfset theVar = "' & obj & '" />';
break;
}

return result;
}

function getDataType(obj) {
var dataType = 'unknown';

if (isSimpleValue(obj)) {
dataType = 'string';
}
if (isArray(obj)) {
dataType = 'array';
}
if (isStruct(obj)) {
dataType = 'struct';
}
if (isQuery(obj)) {
dataType = 'query';
}

return dataType;
}

</cfscript>

<cfset theArray = [12,23,23,345,56] />
<cfset theStruct = { test="yes", age=45, message="hello" } />
<cfset theString = "Hello" />

<cfoutput>
String Result: <cfdump var="#inspect(theString)#"><br />
Array Result: <cfdump var="#inspect(theArray)#"><br />
Structure Result: <cfdump var="#inspect(theStruct)#"><br />
</cfoutput>

Comment 15 by Sean Corfield posted on 9/12/2008 at 9:43 PM

Nice Justice! Now, how about making it work for nested structs of structs and arrays and nested arrays of arrays and structs? :)

Comment 16 by shag posted on 9/12/2008 at 11:30 PM

sounds like someone's being used for free broadchoice development to me.......

Comment 17 by Raymond Camden posted on 9/12/2008 at 11:32 PM

@shag - heh, Groovy already supports this. ;)

Comment 18 by shag posted on 9/12/2008 at 11:41 PM

thats groovy, but it was funny that @sean piped in. his comments sounded kinda like, "yeah, but we needed it to do this". i just thought it was funny.. shoulda taged it with a wink. ;-)