ColdFusion Puzzler - Inspect It!

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 <= arrayLen(arr); i++) { r &= arr[i]; if(i < 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!

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. ;-)