3

In versions of ColdFusion prior to 8 the duplicate function throws an error if there are any components in the structure. In 8 and beyond it will work, but there are issues when copying components.

So, What I need is a way to create a deep copy of a structure that ignores components. For my purposes it's for debugging, I need a snapshot of the variables scope at a particular point in the code, so efficiency doesn't really matter as this will never make it out of the development environment. Currently using CF 7, I would take what 8 offers if only to solve this immediate issue, but I don't control upgrade :(

invertedSpear
  • 10,864
  • 5
  • 39
  • 77

3 Answers3

6

While you were off killing brain cells, I took a stab at a recursive function ;) It excludes components and java/com objects. Neither of which MX7 can duplicate. I threw the functions into a component to avoid tampering with the variables scope. Then stored the instance in the request scope.

It is not rigorously tested. So I am sure there is room for improvement.

Usage

<cfset request.util = createObject("component", "Util")>
<cfset request.copy = request.util.duplicateStructMinusObjects(variables)>
<cfdump var="#request.copy#">

Util.cfc

<cfcomponent>
    <cfscript>
            function duplicateArrayMinusObjects(input) {
                    var x      = "";
                    var value  = "";
                    var output = arrayNew(1);

                    for (x = 1; x lte arrayLen(arguments.input); x = x + 1) {
                            value = arguments.input[x];

                            // note components are considered structures
                            if (IsStruct(value) and not IsObject(value)) {
                                    arrayAppend(output, duplicateStructMinusObjects(value));
                            }
                            else if (IsArray(value)) {
                                    arrayAppend(output, duplicateArrayMinusObjects(value));                
                            }
                            else if (not IsObject(value)){        
                                    arrayAppend(output, duplicate(value));
                            }
                    }        
                    return output;
            }

            function duplicateStructMinusObjects(input) {
                    var key    = "";
                    var value  = "";
                    var output = structNew();

                    for (key in arguments.input) {
                            value = arguments.input[key];

                            // note components are considered structures
                            if (IsStruct(value) and not IsObject(value)) {
                                    output[key] = duplicateStructMinusObjects(value);
                            }
                            else if (IsArray(value)) {
                                    output[key] = duplicateArrayMinusObjects(value);
                            }
                            else if (not IsObject(value)){        
                                    output[key] = duplicate(value);
                            }
                    }

                    return output;
            }
    </cfscript>
</cfcomponent> 
Leigh
  • 28,765
  • 10
  • 55
  • 103
  • I thought about this approach once read the question in RSS :) – Sergey Galashyn Sep 17 '11 at 09:27
  • Great minds ;) (That has happened to me on a few of your responses). The end result was a bit simpler than I first thought. Thank goodness for recursion. – Leigh Sep 17 '11 at 22:46
  • 1
    Take a look at the Memento Design Pattern - when you hit an object I tend to call the Memento and get it to return, as a struct, all the properties of that object in its current state. Was a great way of debugging things like session state and meant I wasn't dumping out the physical objects. – James Buckingham Sep 19 '11 at 09:29
  • I have used it as well. Good thought. – Leigh Sep 19 '11 at 13:34
1

Doesn't matter how long you think/search, you always come up with the answer right after you ask the question.

I was able to solve this by deliberately mis-using try/catch, so I looped through the structure, did a try on creating an object out of a each item as if it were a component, and on error, copied it to my snapshot structure. I also had to store it in a different scope, in my case I used session, since if I let it go to the default variables, there would be a circular reference that cause a structure with an infinite number of children.

EDIT: THIS DOES NOT DO WHAT I THOUGHT IT DID, SEE BELOW

<cfset session.varSnapShot = StructNew()>
<cfset loopList = StructKeyList(variables)>
<cfloop from="1" to="#ListLen(loopList)#" index="i">
    <cftry>
        <cfobject name="x#i#" component="#variables[ListGetAt(loopList,i)]#">
        <cfcatch>
            <cfset session.varSnapShot[ListGetAt(loopList,i)]= variables[ListGetAt(loopList,i)]>
        </cfcatch>
    </cftry>
</cfloop>

EDIT: Since the above doesn't actually do a deep copy (thanks Leigh) I came up with this:

<cfloop from="1" to="#ListLen(loopList)#" index="i">
    <cfset metaData = GetMetaData(variables[ListGetAt(loopList,i)])>
    <cfif isStruct(metaData) AND isDefined("metaData.type") AND metaData.type EQ "component">
    <cfelse>
        <cfset session.varSnapShot[ListGetAt(loopList,i)]= duplicate(variables[ListGetAt(loopList,i)])>
    </cfif>
</cfloop>

This does make a deep copy but will still be a problem if a component is below the first level of an object. I wanted to create a recursive method, but It's an hour and a half past quitting time on a Friday. I will instead kill brain cells at the pub and maybe update this with the recursive method on Monday if I don't forget.

invertedSpear
  • 10,864
  • 5
  • 39
  • 77
  • 1
    I think you forget the `duplicate` ;) I suspect it is trickier than that. If any components are in a nested object you are back to square one. You would need a recursive function. – Leigh Sep 16 '11 at 22:59
  • good point, At this stage I have an object of references, so I only have the variables I have at that time, but not necessarily the same values. Recursion is a must too. I'll try to refactor in case anyone else needs something similar. – invertedSpear Sep 17 '11 at 00:34
0

a modern update to this question:

why not just use #serializeJSON(deserializeJson(theObject))#

that way you get deep copy with no components.

Dawesi
  • 568
  • 3
  • 9