mirror of
https://github.com/MontFerret/ferret.git
synced 2025-07-15 01:25:00 +02:00
Feature/#10 merge recursive (#140)
This commit is contained in:
@ -4,11 +4,12 @@ import "github.com/MontFerret/ferret/pkg/runtime/core"
|
|||||||
|
|
||||||
func NewLib() map[string]core.Function {
|
func NewLib() map[string]core.Function {
|
||||||
return map[string]core.Function{
|
return map[string]core.Function{
|
||||||
"HAS": Has,
|
"HAS": Has,
|
||||||
"KEYS": Keys,
|
"KEYS": Keys,
|
||||||
"KEEP": Keep,
|
"KEEP": Keep,
|
||||||
"MERGE": Merge,
|
"MERGE": Merge,
|
||||||
"ZIP": Zip,
|
"ZIP": Zip,
|
||||||
"VALUES": Values,
|
"VALUES": Values,
|
||||||
|
"MERGE_RECURSIVE": MergeRecursive,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
66
pkg/stdlib/objects/merge_recursive.go
Normal file
66
pkg/stdlib/objects/merge_recursive.go
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
package objects
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MergeRecursive recursively merge the given objects into a single object.
|
||||||
|
// @params objs (Objects) - objects to merge.
|
||||||
|
// @returns (Object) - Object created by merging.
|
||||||
|
func MergeRecursive(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||||
|
err := core.ValidateArgs(args, 1, core.MaxArgs)
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, arg := range args {
|
||||||
|
if err = core.ValidateType(arg, core.ObjectType); err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
merged := values.NewObject()
|
||||||
|
|
||||||
|
for _, arg := range args {
|
||||||
|
merged = merge(merged, arg).(*values.Object)
|
||||||
|
}
|
||||||
|
|
||||||
|
return merged.Clone(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func merge(src, dst core.Value) core.Value {
|
||||||
|
if src.Type() != dst.Type() {
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
if src.Type() != core.ObjectType {
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
srcObj := src.(*values.Object)
|
||||||
|
dstObj := dst.(*values.Object)
|
||||||
|
|
||||||
|
if dstObj.Length() == 0 {
|
||||||
|
return src
|
||||||
|
}
|
||||||
|
|
||||||
|
keyObj := values.NewString("")
|
||||||
|
exists := values.NewBoolean(false)
|
||||||
|
var srcVal core.Value
|
||||||
|
|
||||||
|
dstObj.ForEach(func(val core.Value, key string) bool {
|
||||||
|
keyObj = values.NewString(key)
|
||||||
|
|
||||||
|
if srcVal, exists = srcObj.Get(keyObj); exists {
|
||||||
|
val = merge(srcVal, val)
|
||||||
|
}
|
||||||
|
|
||||||
|
srcObj.Set(keyObj, val)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
return src
|
||||||
|
}
|
236
pkg/stdlib/objects/merge_recursive_test.go
Normal file
236
pkg/stdlib/objects/merge_recursive_test.go
Normal file
@ -0,0 +1,236 @@
|
|||||||
|
package objects_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
"github.com/MontFerret/ferret/pkg/stdlib/objects"
|
||||||
|
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMergeRecursive(t *testing.T) {
|
||||||
|
Convey("Wrong arguments", t, func() {
|
||||||
|
Convey("It should error when 0 arguments", func() {
|
||||||
|
actual, err := objects.MergeRecursive(context.Background())
|
||||||
|
|
||||||
|
So(err, ShouldBeError)
|
||||||
|
So(actual.Compare(values.None), ShouldEqual, 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("It should error when there is not object arguments", func() {
|
||||||
|
actual, err := objects.MergeRecursive(context.Background(), values.NewInt(0))
|
||||||
|
|
||||||
|
So(err, ShouldBeError)
|
||||||
|
So(actual.Compare(values.None), ShouldEqual, 0)
|
||||||
|
|
||||||
|
actual, err = objects.MergeRecursive(context.Background(),
|
||||||
|
values.NewInt(0), values.NewObject(),
|
||||||
|
)
|
||||||
|
|
||||||
|
So(err, ShouldBeError)
|
||||||
|
So(actual.Compare(values.None), ShouldEqual, 0)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Merge single object", t, func() {
|
||||||
|
obj := values.NewObjectWith(
|
||||||
|
values.NewObjectProperty("a", values.NewInt(0)),
|
||||||
|
)
|
||||||
|
expected := values.NewObjectWith(
|
||||||
|
values.NewObjectProperty("a", values.NewInt(0)),
|
||||||
|
)
|
||||||
|
|
||||||
|
actual, err := objects.MergeRecursive(context.Background(), obj)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(actual.Compare(expected), ShouldEqual, 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Merge two objects", t, func() {
|
||||||
|
Convey("When there are no common keys", func() {
|
||||||
|
obj1 := values.NewObjectWith(
|
||||||
|
values.NewObjectProperty("a", values.NewInt(0)),
|
||||||
|
)
|
||||||
|
obj2 := values.NewObjectWith(
|
||||||
|
values.NewObjectProperty("b", values.NewInt(1)),
|
||||||
|
)
|
||||||
|
expected := values.NewObjectWith(
|
||||||
|
values.NewObjectProperty("a", values.NewInt(0)),
|
||||||
|
values.NewObjectProperty("b", values.NewInt(1)),
|
||||||
|
)
|
||||||
|
|
||||||
|
actual, err := objects.MergeRecursive(context.Background(), obj1, obj2)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(actual.Compare(expected), ShouldEqual, 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("When objects with the same key", func() {
|
||||||
|
obj1 := values.NewObjectWith(
|
||||||
|
values.NewObjectProperty("a", values.NewInt(0)),
|
||||||
|
values.NewObjectProperty("b", values.NewInt(10)),
|
||||||
|
)
|
||||||
|
obj2 := values.NewObjectWith(
|
||||||
|
values.NewObjectProperty("c", values.NewInt(1)),
|
||||||
|
values.NewObjectProperty("b", values.NewInt(20)),
|
||||||
|
)
|
||||||
|
expected := values.NewObjectWith(
|
||||||
|
values.NewObjectProperty("a", values.NewInt(0)),
|
||||||
|
values.NewObjectProperty("b", values.NewInt(20)),
|
||||||
|
values.NewObjectProperty("c", values.NewInt(1)),
|
||||||
|
)
|
||||||
|
|
||||||
|
actual, err := objects.MergeRecursive(context.Background(), obj1, obj2)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(actual.Compare(expected), ShouldEqual, 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Merge two objects with the same keys", func() {
|
||||||
|
obj1 := values.NewObjectWith(
|
||||||
|
values.NewObjectProperty("a", values.NewInt(0)),
|
||||||
|
values.NewObjectProperty("b", values.NewInt(10)),
|
||||||
|
)
|
||||||
|
obj2 := values.NewObjectWith(
|
||||||
|
values.NewObjectProperty("a", values.NewInt(1)),
|
||||||
|
values.NewObjectProperty("b", values.NewInt(20)),
|
||||||
|
)
|
||||||
|
expected := values.NewObjectWith(
|
||||||
|
values.NewObjectProperty("a", values.NewInt(1)),
|
||||||
|
values.NewObjectProperty("b", values.NewInt(20)),
|
||||||
|
)
|
||||||
|
|
||||||
|
actual, err := objects.MergeRecursive(context.Background(), obj1, obj2)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(actual.Compare(expected), ShouldEqual, 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("When there are nested arrays", func() {
|
||||||
|
obj1 := values.NewObjectWith(
|
||||||
|
values.NewObjectProperty("a", values.NewArrayWith(
|
||||||
|
values.NewInt(1), values.NewInt(2),
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
obj2 := values.NewObjectWith(
|
||||||
|
values.NewObjectProperty("b", values.NewArrayWith(
|
||||||
|
values.NewInt(1), values.NewInt(2),
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
expected := values.NewObjectWith(
|
||||||
|
values.NewObjectProperty("a", values.NewArrayWith(
|
||||||
|
values.NewInt(1), values.NewInt(2),
|
||||||
|
)),
|
||||||
|
values.NewObjectProperty("b", values.NewArrayWith(
|
||||||
|
values.NewInt(1), values.NewInt(2),
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
|
||||||
|
actual, err := objects.MergeRecursive(context.Background(), obj1, obj2)
|
||||||
|
|
||||||
|
log.Println(actual)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(actual.Compare(expected), ShouldEqual, 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("When there are nested objects (example from ArangoDB doc)", func() {
|
||||||
|
// { "user-1": { "name": "Jane", "livesIn": { "city": "LA" } } }
|
||||||
|
obj1 := values.NewObjectWith(
|
||||||
|
values.NewObjectProperty(
|
||||||
|
"user-1", values.NewObjectWith(
|
||||||
|
values.NewObjectProperty(
|
||||||
|
"name", values.NewString("Jane"),
|
||||||
|
),
|
||||||
|
values.NewObjectProperty(
|
||||||
|
"livesIn", values.NewObjectWith(
|
||||||
|
values.NewObjectProperty(
|
||||||
|
"city", values.NewString("LA"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
// { "user-1": { "age": 42, "livesIn": { "state": "CA" } } }
|
||||||
|
obj2 := values.NewObjectWith(
|
||||||
|
values.NewObjectProperty(
|
||||||
|
"user-1", values.NewObjectWith(
|
||||||
|
values.NewObjectProperty(
|
||||||
|
"age", values.NewInt(42),
|
||||||
|
),
|
||||||
|
values.NewObjectProperty(
|
||||||
|
"livesIn", values.NewObjectWith(
|
||||||
|
values.NewObjectProperty(
|
||||||
|
"state", values.NewString("CA"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
// { "user-1": { "age": 42, "livesIn": { "city": "LA", "state": "CA" }, "name": "Jane" } }
|
||||||
|
expected := values.NewObjectWith(
|
||||||
|
values.NewObjectProperty(
|
||||||
|
"user-1", values.NewObjectWith(
|
||||||
|
values.NewObjectProperty(
|
||||||
|
"age", values.NewInt(42),
|
||||||
|
),
|
||||||
|
values.NewObjectProperty(
|
||||||
|
"name", values.NewString("Jane"),
|
||||||
|
),
|
||||||
|
values.NewObjectProperty(
|
||||||
|
"livesIn", values.NewObjectWith(
|
||||||
|
values.NewObjectProperty(
|
||||||
|
"state", values.NewString("CA"),
|
||||||
|
),
|
||||||
|
values.NewObjectProperty(
|
||||||
|
"city", values.NewString("LA"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
actual, err := objects.MergeRecursive(context.Background(), obj1, obj2)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(actual.Compare(expected), ShouldEqual, 0)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Merged object should be independent of source objects", t, func() {
|
||||||
|
Convey("When array", func() {
|
||||||
|
arr := values.NewArrayWith(values.NewInt(1), values.NewInt(2))
|
||||||
|
obj := values.NewObjectWith(values.NewObjectProperty("arr", arr))
|
||||||
|
|
||||||
|
actual, err := objects.MergeRecursive(context.Background(), obj)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(actual.Compare(obj), ShouldEqual, 0)
|
||||||
|
|
||||||
|
arr.Push(values.NewInt(0))
|
||||||
|
|
||||||
|
So(actual.Compare(obj), ShouldNotEqual, 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("When object", func() {
|
||||||
|
nested := values.NewObjectWith(
|
||||||
|
values.NewObjectProperty("nested", values.NewInt(0)),
|
||||||
|
)
|
||||||
|
obj := values.NewObjectWith(values.NewObjectProperty("obj", nested))
|
||||||
|
|
||||||
|
actual, err := objects.MergeRecursive(context.Background(), obj)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(actual.Compare(obj), ShouldEqual, 0)
|
||||||
|
|
||||||
|
nested.Set(values.NewString("str"), values.NewInt(0))
|
||||||
|
|
||||||
|
So(actual.Compare(obj), ShouldNotEqual, 0)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
Reference in New Issue
Block a user