Skip to main content
Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.

StructCopy() Does Not Necessarily Return A Native Struct In Lucee CFML 5.3.3.62

By Ben Nadel on
Tags: ColdFusion

Over the weekend, while working on a version of dump() that gracefully handles cyclic references in Lucee CFML, I happened upon a quirk of the language when dealing with reflection-style coding. It turns out, using structCopy() to perform a shallow-copy of a given data-structure doesn't necessarily return a native Struct. By which, I mean, the returned object doesn't necessarily have Struct member methods like .each(), .map(), and .filter(). Since this tripped me up, I thought I should write it down so that I don't forget how things work in Lucee CFML 5.3.3.62.

Perhaps naively, I had always had a mental model of structCopy() that was akin to creating a new Struct and then appending all of the keys (ie, shallow-copy) defined in the source Struct. It turns out, structCopy() is a bit more complicated than that. It seems that structCopy() actually copies the root structure itself, and then appends the keys.

To give some context here, let's look at some simple ColdFusion code:

<cfscript>

	thing = new Thing();

	// Since a ColdFusion component isn't a "native" Struct, we are going to try and
	// copy it so that we can get the native Struct member-methods.
	// --
	// CAUTION: This approach does not work in Lucee CFML 5.3.3.62.
	structCopy( thing ).each(
		( key, value ) => {

			echo( key & ", " );

		}
	);

</cfscript>

Here, we're dealing with a ColdFusion Component, which is Struct-like; but, is not a native Struct, which means it doesn't have member-methods like .each(). As such, I'm attempting to use structCopy() to coerce the top-level data-structure into a native Struct. However, when we run this ColdFusion code in Lucee CFML 5.3.3.62, we get the following error:

Passing a ColdFusion component to structCopy() does not produce a native Struct in Lucee CFML 5.3.3.62.

As you can see, when I pass a ColdFusion component to structCopy(), the returned value doesn't have the Struct-native member method, .each(). And, in fact, if we look at the error message, we can see that the returned value is actually of type, Thing - the same class as our original ColdFusion Component.

In fact, we can see this root-level Type if we use isInstanceOf():

<cfscript>

	thing = new Thing();

	// Check to see if both values are considered instances of the Thing component.
	echo( isInstanceOf( thing, "Thing" ) & "<br />" );
	echo( isInstanceOf( structCopy( thing ), "Thing" ) & "<br />" );

</cfscript>

If we run this ColdFusion code in Lucee CFML 5.3.3.62, we get the following output:

true
true

As you can see, structCopy( thing ) is an instance of Thing. Which means that structCopy() isn't simply creating Structs - it's actually copying the root data-type as part of the shallow-copy. This means that we can't use structCopy() to coerce a value into a native Struct.

ASIDE: A nice feature of this structCopy() behavior is that it maintains the original type of Struct. So, if my Struct implementation is an "Ordered Struct", my structCopy() will also return an Ordered Struct.

At this point, I have two options. I can either fall-back to using the more generic Struct built-in functions (BIFs), like structEach():

<cfscript>

	thing = new Thing();

	// Since a ColdFusion component isn't a "native" Struct, we can't use the Struct
	// member methods. However, we can still use the more generic Struct built-in
	// functions.
	structEach(
		thing,
		( key, value ) => {

			echo( key & ", " );

		}
	);

</cfscript>

... which will happily iterate over the "Struct like" ColdFusion Component. Or, I can create my own User Defined Function (UDF) that operates more like my original mental mode of structCopy():

<cfscript>

	thing = new Thing();

	// Since a ColdFusion component isn't a "native" Struct, we are going to try and
	// copy it so that we can get the native Struct member-methods.
	structCopyNative( thing ).each(
		( key, value ) => {

			echo( key & ", " );

		}
	);

	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //

	/**
	* I perform a shallow-copy of the given data, returning a native Struct.
	* 
	* @value I am the data being copied.
	*/
	public struct function structCopyNative( required any value ) {

		var copy = {};
		// By appending the incoming value, we're essentially performing a shallow-copy
		// of the value; but, we're keeping the native data-type of the Struct (which
		// ensures that the member-methods exist).
		structAppend( copy, value );

		return( copy );

	}

</cfscript>

Here, you can see that I am implementing an algorithm that exactly matches my naive mental model: I'm creating a native Struct; and then, I'm appending / performing a shallow-copy of the source data-structure. Which works perfectly well, but obliviously requires more work.

Once of my favorite features in the modern releases of ColdFusion is the addition of member-methods. This allows my ColdFusion code to look and feel very much like the code that I would write in other languages (ex, JavaScript). However, I have to remember that when I get into reflection-style programming, I have to take a more cautious view of data-structures (since I am not the one created them). And, fall-back to using the more flexible built-in functions, like structEach().



Reader Comments

What has two thumbs and hopes you leave a comment? This Guy! (Ben Nadel).

Post A Comment

You — Get Out Of My Dreams, Get Into My Blog
Live in the Now
Oops!
NEW: Some basic markdown formatting is now supported: bold, italic, blockquotes, lists, fenced code-blocks. Read more about markdown syntax »
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.