builtins: add object.union_n

Fixes: #4012

Signed-off-by: Anders Eknert <anders@eknert.com>
This commit is contained in:
Anders Eknert
2022-01-22 23:46:04 +01:00
parent c9b6a3c5e3
commit caffcec07b
5 changed files with 129 additions and 1 deletions
+15
View File
@@ -157,6 +157,7 @@ var DefaultBuiltins = [...]*Builtin{
// Object Manipulation
ObjectUnion,
ObjectUnionN,
ObjectRemove,
ObjectFilter,
ObjectGet,
@@ -1330,6 +1331,20 @@ var ObjectUnion = &Builtin{
),
}
// ObjectUnionN creates a new object that is the asymmetric union of all objects merged from left to right
var ObjectUnionN = &Builtin{
Name: "object.union_n",
Decl: types.NewFunction(
types.Args(
types.NewArray(
nil,
types.NewObject(nil, types.NewDynamicProperty(types.A, types.A)),
),
),
types.A,
),
}
// ObjectRemove Removes specified keys from an object
var ObjectRemove = &Builtin{
Name: "object.remove",
+25
View File
@@ -2558,6 +2558,31 @@
"type": "function"
}
},
{
"name": "object.union_n",
"decl": {
"args": [
{
"dynamic": {
"dynamic": {
"key": {
"type": "any"
},
"value": {
"type": "any"
}
},
"type": "object"
},
"type": "array"
}
],
"result": {
"type": "any"
},
"type": "function"
}
},
{
"name": "opa.runtime",
"decl": {
+2 -1
View File
@@ -343,7 +343,8 @@ complex types.
| ------- |-------------|---------------|
| <span class="opa-keep-it-together">`value := object.get(object, key, default)`</span> | `value` is the value stored by the `object` at `key`. If no value is found, `default` is returned. | ✅ |
| <span class="opa-keep-it-together">`output := object.remove(object, keys)`</span> | `output` is a new object which is the result of removing the specified `keys` from `object`. `keys` must be either an array, object, or set of keys. | ✅ |
| <span class="opa-keep-it-together">`output := object.union(objectA, objectB)`</span> | `output` is a new object which is the result of an asymmetric recursive union of two objects where conflicts are resolved by choosing the key from the right-hand object (`objectB`). For example: `object.union({"a": 1, "b": 2, "c": {"d": 3}}, {"a": 7, "c": {"d": 4, "e": 5}})` will result in `{"a": 7, "b": 2, "c": {"d": 4, "e": 5}}` | ✅ |
| <span class="opa-keep-it-together">`output := object.union(objectA, objectB)`</span> | `output` is a new object which is the result of an asymmetric recursive union of two objects where conflicts are resolved by choosing the key from the right-hand object (`objectB`). For example: `object.union({"a": 1, "b": 2, "c": {"d": 3}}, {"a": 7, "c": {"d": 4, "e": 5}})` will result in `{"a": 7, "b": 2, "c": {"d": 4, "e": 5}}` | ✅ |
| <span class="opa-keep-it-together">`output := object.union_n(array)`</span> | `output` is a new object which is the result of an asymmetric recursive union of all objects in `array`, merged from left to right, where conflicts are resolved by choosing the key from the right-hand object. For example: `object.union_n([{"a": 1}, {"b": 2}, {"a": 3}])` will result in `{"b": 2, "a": 3}` | ``SDK-dependent`` |
| <span class="opa-keep-it-together">`filtered := object.filter(object, keys)`</span> | `filtered` is a new object with the remaining data from `object` with only keys specified in `keys` which is an array, object, or set of keys. For example: `object.filter({"a": {"b": "x", "c": "y"}, "d": "z"}, ["a"])` will result in `{"a": {"b": "x", "c": "y"}}`). | ✅ |
| <span class="opa-keep-it-together">`filtered := json.filter(object, paths)`</span> | `filtered` is the remaining data from `object` with only keys specified in `paths` which is an array or set of JSON string paths. For example: `json.filter({"a": {"b": "x", "c": "y"}}, ["a/b"])` will result in `{"a": {"b": "x"}}`). Paths are not filtered in-order and are deduplicated before being evaluated. | ✅ |
| <span class="opa-keep-it-together">`output := json.remove(object, paths)`</span> | `output` is a new object which is the result of removing all keys specified in `paths` which is an array or set of JSON string paths. For example: `json.remove({"a": {"b": "x", "c": "y"}}, ["a/b"])` will result in `{"a": {"c": "y"}}`. Paths are not removed in-order and are deduplicated before being evaluated. | ✅ |
@@ -0,0 +1,67 @@
cases:
- note: objectunionn/empty array
modules:
- |
package test
p := object.union_n([{}])
query: data.test.p = x
want_result:
- x: {}
- note: objectunionn/single item array
modules:
- |
package test
p := object.union_n([{"foo": "bar"}])
query: data.test.p = x
want_result:
- x: {"foo": "bar"}
- note: objectunionn/merge objects
modules:
- |
package test
x := object.union_n([{"foo": "bar"}, {"x": "y"}])
query: data.test.x = x
want_result:
- x: {"foo": "bar", "x": "y"}
- note: objectunionn/merge objects conflict
modules:
- |
package test
x := object.union_n([{"foo": "bar"}, {"foo": "baz"}])
query: data.test.x = x
want_result:
- x: {"foo": "baz"}
- note: objectunionn/merge objects extended
modules:
- |
package test
x := object.union_n([{
"a": 1,
"b": 2,
"c": 3,
}, {
"foo": "baz",
"a": "a",
"b": 2,
"d": 4,
}, {
"a": "final A!",
"e": 5.0,
}])
query: data.test.x = x
want_result:
- x: {"foo": "baz", "a": "final A!", "b": 2, "c": 3, "d": 4, "e": 5.0}
+20
View File
@@ -26,6 +26,25 @@ func builtinObjectUnion(_ BuiltinContext, operands []*ast.Term, iter func(*ast.T
return iter(ast.NewTerm(r))
}
func builtinObjectUnionN(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error {
arr, err := builtins.ArrayOperand(operands[0].Value, 1)
if err != nil {
return err
}
r := ast.NewObject()
arr.Foreach(func(t *ast.Term) {
var o ast.Object
o, err = builtins.ObjectOperand(t.Value, 1)
r = mergeWithOverwrite(r, o)
})
if err != nil {
return err
}
return iter(ast.NewTerm(r))
}
func builtinObjectRemove(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error {
// Expect an object and an array/set/object of keys
obj, err := builtins.ObjectOperand(operands[0].Value, 1)
@@ -134,6 +153,7 @@ func mergeWithOverwrite(objA, objB ast.Object) ast.Object {
func init() {
RegisterBuiltinFunc(ast.ObjectUnion.Name, builtinObjectUnion)
RegisterBuiltinFunc(ast.ObjectUnionN.Name, builtinObjectUnionN)
RegisterBuiltinFunc(ast.ObjectRemove.Name, builtinObjectRemove)
RegisterBuiltinFunc(ast.ObjectFilter.Name, builtinObjectFilter)
RegisterBuiltinFunc(ast.ObjectGet.Name, builtinObjectGet)