From 28dcfb9e4ce952214ba5bd7284b96dbb05088d32 Mon Sep 17 00:00:00 2001 From: George Suntres Date: Thu, 23 Apr 2026 15:37:20 -0400 Subject: [PATCH] Add bson clone --- struct.go | 50 +++++++++++++++++++++++++++++++++++++++++++++++++- struct_test.go | 28 ++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 1 deletion(-) diff --git a/struct.go b/struct.go index 5e1c148..b6609da 100644 --- a/struct.go +++ b/struct.go @@ -76,7 +76,7 @@ func MapMerge[T ~map[string]any](maps...T) T { } // StructMustTomap given any struct return the equivalent map[string]any or nil. -// Will never throw. +// Will never throw. Will also work for map[string]any. func StructMustToMap(data any) map[string]any { b, err := json.Marshal(data) if err != nil { @@ -164,6 +164,54 @@ func StructToMapRecursive(obj any) any { } } +func BsonClone(m map[string]any) map[string]any { + if m == nil { + return nil + } + + out := make(map[string]any, len(m)) + + for k, v := range m { + out[k] = deepCopy(v) + } + + return out +} + +func deepCopy(v any) any { + switch val := v.(type) { + case bson.M: + return BsonClone(map[string]any(val)) + case bson.A: + arr := make([]any, len(val)) + for i, item := range val { + arr[i] = deepCopy(item) + } + return arr + case bson.D: + d := make(bson.D, len(val)) + for i, elem := range val { + d[i] = bson.E{ + Key: elem.Key, + Value: deepCopy(elem.Value), + } + } + return d + case map[string]any: // bson.M + return BsonClone(val) + case []any: // bson.A + arr := make([]any, len(val)) + for i, item := range val { + arr[i] = deepCopy(item) + } + return arr + + default: + // primitives (string, int, bool, etc.) + return val + } +} + func BsonDGetAny(d bson.D, key string) (any, bool) { for _, e := range d { if e.Key == key { diff --git a/struct_test.go b/struct_test.go index 1878553..291ed51 100644 --- a/struct_test.go +++ b/struct_test.go @@ -1,6 +1,7 @@ package commons import ( + "fmt" "testing" ) @@ -307,4 +308,31 @@ func TestMapMerge(t *testing.T) { if len(m) != 4 { t.Fatalf("Merged map should have lenght 4 not %d", len(m)) } +} + +func TestBsonClone(t *testing.T) { + orig := map[string]any{ + "name": "Kolias", + "age": int32(56), + "salary": int64(100000000), + "meta": map[string]any{ + "num1": int32(110), + }, + } + + clone := BsonClone(orig) + + if "int32" != fmt.Sprintf("%T", clone["age"]) { + t.Fatalf("int32 should map to int32") + } + + if "int64" != fmt.Sprintf("%T", clone["salary"]) { + t.Fatalf("int64 should map to int64") + } + + meta := clone["meta"].(map[string]any) + + if "int32" != fmt.Sprintf("%T", meta["num1"]) { + t.Fatalf("netsted type should have been int32") + } } \ No newline at end of file