Getting the union of two structs

January 26, 2019
I try to bend the internet to my will.
Wizard 26 posts
Followers: 19 people
0

Getting the union of two structs

I try to bend the internet to my will.
Wizard 26 posts
Followers: 19 people
January 26, 2019

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!

Comments (0)
Add your comment