2
\$\begingroup\$

I have an application that has a lot of very complex objects that in most cases I only need to access an id value and a name value from. I'd like to reduce the overhead of serializing these large objects to JSON. This is a simple function I created that takes an array of objects and returns an array of structures that contains only two keys, an id and a value. This also needs to be compatible with CF8 servers.

I'd like to get some feedback on ways to make the code more expressive and any ways I can optimize this as well.

<cffunction name="toKeyValuePairArray" returntype="Array" output="false" access="public">
    <cfargument name="theArray" type="Array" required="true" hint="an array of objects" />
    <cfargument name="idGetterFunctionName" type="string" />
    <cfargument name="valueGetterFunctionName" type="string" />
        <cfset var local = structNew() />
        <cfset var retArray = [] />
        <cfset var obj = {} />
        <cfloop array="#arguments.theArray#" index="local.theComponent">
            <cfset obj = {} />
            <cftry>
                <cfinvoke component="#local.theComponent#" method="#arguments.idGetterFunctionName#" returnvariable="local.id" />
                <cfinvoke component="#local.theComponent#" method="#arguments.valueGetterFunctionName#" returnvariable="local.value" />

                <cfset obj.id = local.id />
                <cfset obj.value = local.id />

                <cfset arrayAppend(retArray,obj) />
            <cfcatch></cfcatch>
            </cftry>
        </cfloop> 

        <cfreturn retArray />
</cffunction>
\$\endgroup\$
3
  • \$\begingroup\$ I do not see the code to review. To ask a question, there is a stackoverflow.com site \$\endgroup\$ Oct 3 '11 at 6:59
  • \$\begingroup\$ Thats strange, i can see the code from my iPhone right now. I'll take another look when i get to my computer. \$\endgroup\$ Oct 3 '11 at 10:23
  • \$\begingroup\$ @ArturMustafin I just checked on my computer too and it looks OK. Is this not what you are seeing? http://ow.ly/i/irvj \$\endgroup\$ Oct 3 '11 at 11:57
2
\$\begingroup\$

Ryan,

Here's how I would do it. Since you're making something backwards compatible with CF8, which does not explicitly have the 'Local' scope, and you've already declared it via the 'var', then we'll put all other function local variables within this struct, and only return that which is needed.

<!--- 
 *  FUNCTION toKeyValuePairArray
 *  Method to create a simple object reference array to reduce overhead and improve performance.
 *  This assumes that your objects have Getters for their primary properties.
 *  (no need to instantiate every object, every time, when all you need is a number or a name)
 *  
 *  @theArray (array) - an array of objects
 *  @idGetterFunctionName -  Function name for getter function that pulls the object's id
 *  @valueGetterFunctionName - Function name for getter function that pulls the object's name
 --->
<cffunction name="ToKeyValuePairArray" returntype="array" output="false" access="public">
    <cfargument name="theArray" type="Array" required="true" hint="an array of objects" />
    <cfargument name="idGetterFunctionName" required="true" type="string" hint="Function name for getter function that pulls the object's id" />
    <cfargument name="valueGetterFunctionName" required="true" type="string" hint="Function name for getter function that pulls the object's name" />

    <cfset var LOCAL = StructNew() />
    <cfset LOCAL.retArray = ArrayNew(1) />

    <cfloop array="#ARGUMENTS.theArray#" index="LOCAL.theComponent">
        <cfset LOCAL.tmpObjVals = StructNew() />
        <cftry>
            <cfinvoke component="#LOCAL.theComponent#" method="#ARGUMENTS.idGetterFunctionName#" returnvariable="LOCAL.id" />
            <cfinvoke component="#LOCAL.theComponent#" method="#ARGUMENTS.valueGetterFunctionName#" returnvariable="LOCAL.value" />

            <cfset LOCAL.tmpObjVals["id"] = LOCAL.id />
            <cfset LOCAL.tmpObjVals["value"] = LOCAL.value />

            <cfset ArrayAppend(LOCAL.retArray,Duplicate(LOCAL.tmpObjVals)) />
            <cfcatch><!--- Empty Catch ---></cfcatch>
        </cftry>
    </cfloop> 

    <cfreturn LOCAL.retArray />
</cffunction>

Since we 'var' one core variable (creating our own function local scope, so to speak) we no longer have to declare the temp object at the top of the method, since you reset it at the top of each loop iteration anyway. I also standardized your struct and array initialization across the method, to maintain that backward compatibility (and standardization of form is just good practice).

You aren't really doing any additional processing on the return variables from your 'getter' calls, so I applied them directly to the tempObj, so we don't create any unnecessary variables. You don't have to upper case scope names. I do that to make them stand out better when looking at code. I did uppercase the function names. This is just something I got from college: begin uppercase the function calls, and begin lowercase on variable names, then camel case the remainder.

NOTE: Adjusted some of my code, to work in some of Dan's (good) suggestions. I understand the 'try/catch' conceptually, but I did make them required args so it would throw errors if you didn't have them.

\$\endgroup\$
2
  • \$\begingroup\$ Thanks Steve, some good stuff here. Personally I use local. if I feel that the number of vars I'd have to declare on the top of my function would hinder its readability. Its really just a "feel" thing for me. I just don't like having to type local. all the time if I don't have to. \$\endgroup\$ Oct 4 '11 at 15:21
  • \$\begingroup\$ Ryan, call me Cutter. I adjusted my code example, adding back in the lines where you set the cfinvoke returns to the temp object keys. The notation used will preserve the casing of those key names when CF serializes the object into JSON. Unfortunately you can't use that notation in the returnvariable attribute, so you have to jump a few hoops. \$\endgroup\$ Oct 11 '11 at 14:32
2
\$\begingroup\$

A few things:

  1. What's the purpose of the try/catch other than hiding an error? Is it understood that you may have objects that don't have the proper ID and Value functions? If so, should those be flagged somehow in your return?

  2. I would change your obj variable name, so as not to represent that the returned structures are objects, since they're not :). Perhaps objValues instead?

  3. I would change your final arrayAppend to use the Duplicate function to prevent reference issues.

    <cfset arrayAppend(retArray,Duplicate(obj)) />

Other than that, I don't see any issues with the code.

\$\endgroup\$
2
  • \$\begingroup\$ Basically the reason for the try/catch is that I'm going for a completely silent fail, i'll handle any error conditions in the JavaScript function that handles the serialized json response. \$\endgroup\$ Oct 3 '11 at 16:01
  • \$\begingroup\$ Totally makes sense. \$\endgroup\$
    – Dan Short
    Oct 3 '11 at 16:04
1
\$\begingroup\$

I'll ask the obvious/dumb question. Can you access this information from a database?

A simple query for id and name would be easier to work with.

\$\endgroup\$
2
  • \$\begingroup\$ Bascially this function is a way to standardize array's of objects that may have different column names for the value and id. So in our database schemas ID's are typically named accountID, addressID, etc. Using this function I can pass in an array of any type of object and get back a standardized structure serialized into JSON. Then in my JavaScript I can count on the objects having a id and value property and I don't have to write anything specific to a certian type of object. \$\endgroup\$ Oct 4 '11 at 21:01
  • \$\begingroup\$ Ah, I see what you're doing. \$\endgroup\$ Oct 5 '11 at 15:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.