Getting the union of two structs

I’ve recently been blogged about unions, intersections, diffs and disjunctive unions of elements in arrays. I thought I do the same for structs.

First up here’s how to do a union:

function union(required struct a, required struct b) { return a.append(b); } set1 = {"a": 1, "b": 2}; set2 = {"c": 3, "d": 4}; writeDump(union(set1, set2)); // {a:1, b:2, c:3, d:4}

OK, that was easy. Wait a second, not so hasty!

Before we move on, let’s dig a little deeper. What do you think the values of `set1`

and `set2`

are after we called the `union`

function?

Let’s run the code again and take a look.

function union(required struct a, required struct b) { return a.append(b); } set1 = {"a": 1, "b": 2}; set2 = {"c": 3, "d": 4}; writeDump(union(set1, set2)); // {a:1, b:2, c:3, d:4} // check set1 and set2 values writeDump(set1); // {a:1, b:2, c:3, d:4} writeDump(set2); // {c:3, d:4}

As you can see, `set1`

is no longer the same as when we passed it to the function. Instead the function has mutated it. As structs are passed by reference, not by value, in CFML, then any changes to the struct inside the function will act on the original struct. This could be an unwanted side effect and lead to bugs in our code. let’s fix that.

function union(required struct a, required struct b) { return a.duplicate().append(b); } set1 = {"a": 1, "b": 2}; set2 = {"c": 3, "d": 4}; writeDump(union(set1, set2)); // {a:1, b:2, c:3, d:4} // check set1 and set2 values writeDump(set1); // {a:1, b:2} writeDump(set2); // {c:3, d:4}

By adding the `duplicate`

method we end up with a clone of the original struct. Now any changes we make inside the function only apply to the clone and the original variable is left intact.

OK, so we’re done with `union`

. Well, not quite. What happens if you have the same key in both structs? Let’s try that.

function union(required struct a, required struct b) { return a.duplicate().append(b); } set1 = {"a": 1, "b": 2, "c": 3}; set2 = {"c": 3, "d": 4}; writeDump(union(set1, set2)); // {a:1, b:2, c:3, d:4}

Great, our code still returns the expected result. Can we move on now? Sorry, no, we need to know what happens if the key is the same, but the value is different?

function union(required struct a, required struct b) { return a.duplicate().append(b); } set1 = {"a": 1, "b": 2, "c": 31}; set2 = {"c": 32, "d": 4}; writeDump(union(set1, set2)); // {a:1, b:2, c:32, d:4}

We now see that the value of `c`

from `set2`

is the value we get in the returned result. That may suit us, on the other hand, maybe I want the values from `set1`

to take priority. We can do that like so:

function union(required struct a, required struct b) { return a.duplicate().append(b, false); } set1 = {"a": 1, "b": 2, "c": 31}; set2 = {"c": 32, "d": 4}; writeDump(union(set1, set2)); // {a:1, b:2, c:31, d:4}

By passing a value of `false`

as the 2nd parameter of the `append()`

method, we are saying ‘Do not overwrite existing key value pairs’.

One final thing we should check is does the case of the key matter here?

set1 = {"A": 1, "B": 2, "C": 31}; set2 = {"c": 32, "d": 4}; writeDump(union(set1, set2)); // {A:1, B:2, C:31, d:4}

The answer is that a keyname of `C`

is treated as a match for the keyname of `c`

. The case of the key is also preserved in the result.

You may think I’ve gone a bit over the top in my examples (and maybe I have!), but I think it’s useful to know how these things work. Hopefully it’ll save someone some headaches!

You must be logged in to post a comment.