Merge 2 Objects in Rego
Tags: rego, opa, openpolicyagent
Published: 2019-10-15 21:25:36

This is the final post of "Object Merging in Rego" series.

If you are not comfortable with Rego yet, I'd recommend you read the previous posts before continuing.

When the keys collide with the merging objects, let's say **the latter object's value wins**. With that in mind, in order to merge objects, we need to:

1. Gather the keys of the 2 objects
2. Pick the values of the objects and if the keys collide, pick the latter object's value

In order to implement this, "[Set Comprehensions](https://www.openpolicyagent.org/docs/latest/policy-language/#set-comprehensions)" and "[Object Comprehensions](https://www.openpolicyagent.org/docs/latest/policy-language/#object-comprehensions)" come in handy.

### Gather keys of 2 objects

Suppose we have 2 objects like the following:

##### object1

```json
{
  "a": "value1",
  "b": "value2"
}
```

##### object2

```json
{
  "b": "value3",
  "c": "value4"
}
```

If we're merging these 2 objects, what we first want is the keys `a, b, c`. How can we gather these? With Set comprehensions.

This is how it will look like in Rego:

```
collect_keys = ks {
  ks := {k | some k; _ = input.a[k]} | {k | some k; _ = input.b[k]}
}
```

Maybe it looks a bit difficult for beginners but you'll get used to it sooner or later.

Let's look at one part of it:

```
{k | some k; _ = input.a[k]}
```

This is like saying 

* Iterate over `input.a`'s keys
* Put it inside the local variable `k`
* Collect it

So if `input.a` is `{ "a": "value1", "b": "value2" }`, the result will be `{ "a", "b" }`. In addition, when the `input.b` is `{ "b": "value3", "c": "value4" }`, the result will be `{ "b", "c" }` and the rule above will look like this:

```
collect_keys = ks {
  ks := { "a", "b" } | { "b", "c" }
}
```

Which evaluates to `ks == { "a", "b", "c" }` because it is evaluating an OR with 2 sets.

You can check this out in the following playground:

https://play.openpolicyagent.org/p/bw0aM0bRHM

### Pick values from 2 objects

Now that we have the keys of the final merged object, we need to gather the values of the 2 objects. What we'd want to do is:

1. Iterate over the keys we gathered above
2. Pick the value corresponding with the key. Be sure to prioritize the latter object's value over the former one.

This is where the `pick_first` function comes in handy from the 2nd post of this series.

{% link https://dev.to/kenfdev/picking-the-first-value-you-find-from-2-objects-in-rego-o6c %}

Assuming that `pick_first` has already been declared, the following Rego function shows how 2 objects get merged.

```
merge_objects(a, b) = c {
    ks := {k | some k; _ = a[k]} | {k | some k; _ = b[k]}
    c := {k: v | some k; ks[k]; v := pick_first(k, b, a)}
}
```

Note that the object `b` comes first and object `a` next in the `pick_first` function which means that the values of object `b` are prioritized.

### Wrap up

The final version of merging 2 objects in Rego looks like this (which you can find [here](https://www.openpolicyagent.org/docs/latest/policy-cheatsheet/#merge-objects) in the official docs):

```
has_key(x, k) { _ = x[k] }

pick_first(k, a, b) = a[k]
pick_first(k, a, b) = b[k] { not has_key(a, k) }

merge_objects(a, b) = c {
    ks := {k | some k; _ = a[k]} | {k | some k; _ = b[k]}
    c := {k: v | some k; ks[k]; v := pick_first(k, b, a)}
}

merged = o {
  o := merge_objects(input.x, input.y)
}
```

If the `input` is as follows:

```json
{
  "x": {
    "a": true,
    "b": false
  },
  "y": {
    "b": "foo",
    "c": 4
  }
}
```

The `merged` output becomes like this:

```json
{
  "merged": {
    "a": true,
    "b": "foo",
    "c": 4
  }
}
```

Note that object `y`'s key/value `"b": "foo"` is chosen because it is the latter object when merging.

For those who want to play around, here is the playground code:

https://play.openpolicyagent.org/p/LzJDiUQQFY

### Reference

* [Composite Values](https://www.openpolicyagent.org/docs/latest/policy-language/#composite-values)
* [Comprehensions](https://www.openpolicyagent.org/docs/latest/policy-language/#comprehensions)