Eric Fuller
Eric Fuller

Reputation: 159

Coldfusion: Odd Behavior with Array of Objects

So, I'm experiencing a very frustrating problem. I am making an AJAX call (via jquery.post()) that sends criteria that are used to filter Coldfusion objects.

The processing is pretty straightforward. First, a check is performed to see if certain filters have been passed in. If a filter exists, then the objects in the session scope are looped through and, if the value of one of the object elements matches any items in the list of filter values, that object is added to an array. Once this process completes for all defined filter types, the arrays of matching objects are stored in a results object (obj.results), passed to the serializeJSON function, and then ultimately returned to the client.

Unfortunately, if there's more than one filter in play, it appears as though the loops are exited prematurely, resulting in the return fewer results than there are matches. If I only deal with one of the two filters included below, I get the full expected results. Likewise, if in each of the arrayappend() calls below I change s[i] to s[i].name (so, appending a string instead of an object), the expected number of results is returned for each filter.

Here's the code using s[i]:

<cffunction name="filterObj" access="public" returntype="any">
    <cfargument name="filterParams" type="struct" required="yes">
    <cfset var s = session.a> <!--- session.a IS AN ARRAY OF CF OBJECTS --->
    <cfset filteredResults = {}>
    <cfset filteredResults.eligibilities = []>
    <cfset filteredResults.agencies = []>
    <cfif isdefined("arguments.filterParams.agency")>
        <cfloop from="1" to="#arraylen(s)#" index="i">
            <cfif structkeyexists(s[i],"agency") and
                listfindnocase(arguments.filterParams["agency"],s[i].agency["name"])>
                    <cfset arrayappend(filteredResults.agencies, s[i])>
            </cfif>
        </cfloop>
    </cfif>
    <cfif isdefined("arguments.filterParams.eligibility")>
        <cfloop from="1" to="#arraylen(s)#" index="i">
            <cfif structkeyexists(s[i],"el")>
                <cfloop from="1" to="#arraylen(s[i].eligibility)#" index="e">
                    <cfif listfindnocase(arguments.filterParams["eligibility"],s[i].eligibility[e].type)>
                        <cfset arrayappend(filteredResults.eligibilities, s[i])>
                    </cfif>
                </cfloop>
            </cfif>
        </cfloop>
    </cfif>
    <cfset obj.results = filteredResults>
    <cfset obj = serializeJSON(obj)>
    <cfreturn obj>
</cffunction>

Here are the unexpanded console.logged results using s[i]

RESULTS: Object
  AGENCIES: Array[5]
  ELIGIBILITIES: Array[5]

Here's the code again using s[i].name

<cffunction name="filterObj" access="public" returntype="any">
    <cfargument name="filterParams" type="struct" required="yes">
    <cfset var s = session.a> <!--- session.a IS AN ARRAY OF CF OBJECTS --->
    <cfset filteredResults = {}>
    <cfset filteredResults.eligibilities = []>
    <cfset filteredResults.agencies = []>
    <cfif isdefined("arguments.filterParams.agency")>
        <cfloop from="1" to="#arraylen(s)#" index="i">
            <cfif structkeyexists(s[i],"agency") and
                listfindnocase(arguments.filterParams["agency"],s[i].agency["name"])>
                    <cfset arrayappend(filteredResults.agencies, s[i].name)>
            </cfif>
        </cfloop>
    </cfif>
    <cfif isdefined("arguments.filterParams.eligibility")>
        <cfloop from="1" to="#arraylen(s)#" index="i">
            <cfif structkeyexists(s[i],"el")>
                <cfloop from="1" to="#arraylen(s[i].eligibility)#" index="e">
                    <cfif listfindnocase(arguments.filterParams["eligibility"],s[i].eligibility[e].type)>
                        <cfset arrayappend(filteredResults.eligibilities, s[i].name)>
                    </cfif>
                </cfloop>
            </cfif>
        </cfloop>
    </cfif>
    <cfset obj.results = filteredResults>
    <cfset obj = serializeJSON(obj)>
    <cfreturn obj>
</cffunction>

Here are the unexpanded console.logged results using s[i].name

RESULTS: Object
  AGENCIES: Array[10]
  ELIGIBILITIES: Array[6]

I feel like there must be some kind of asynchronous processing of the two loops happening on the server, or that something's timing out before a loop can finish.

Upvotes: 2

Views: 1831

Answers (1)

Shawn Holmes
Shawn Holmes

Reputation: 3762

There is a known issue in some versions of CF9, as well as CF10, regarding the serialization of an array of objects; you've stumbled across this bug by appending the objects themselves to the return var, as opposed to the string-based keys of said objects.

The bug is reproducible via this simple script:

<cfset obj = ArrayNew(1) />

<cfset obj[1] = StructNew() />
<cfset obj[1].name = "Kate" />
<cfset obj[2] = StructNew() />
<cfset obj[2].name = "Ted" />
<cfset obj[3] = StructNew() />
<cfset obj[3].name = "Phil" />

<cfset data = ArrayNew(1) />

<cfloop from="1" to="#ArrayLen(obj)#" index="i">
    <cfset ArrayAppend(data, obj[i]) />
</cfloop>

<cfdump var=#data#>

<cfloop from="1" to="#ArrayLen(obj)#" index="i">
    <cfset ArrayAppend(data, obj[i]) />
</cfloop>

<cfdump var=#data#>

<cfoutput>#ArrayLen(data)#</cfoutput>
<cfset json = SerializeJSON(data) />

<cfdump var=#json#>

<cfset converted = DeserializeJSON(json) />
<cfoutput>#ArrayLen(converted)#</cfoutput>

The expected result is that the final deserialized array is 6 elements in length, as it was pre-serialization. However, the actual result is 3.

Stick with appending strings, rather than entire objects, to the array var you're returning, if you absolutely must serialize it before returning it from the function, and refactor outlying code that calls this function.

SOURCE: Problem with serializeJSON -- truncates embedded objects

Upvotes: 3

Related Questions