deep merge

This commit is contained in:
mr
2026-06-03 11:23:41 +02:00
parent 9ab374b720
commit 842364d145
2 changed files with 83 additions and 5 deletions
-1
View File
@@ -10,7 +10,6 @@ import (
"cloud.o-forge.io/core/oc-lib/tools" "cloud.o-forge.io/core/oc-lib/tools"
) )
/* /*
* Booking is a struct that represents a booking * Booking is a struct that represents a booking
*/ */
+83 -4
View File
@@ -128,10 +128,8 @@ func ModelGenericUpdateOne(change map[string]interface{}, id string, a Accessor)
r.Sign() r.Sign()
} }
loaded := r.Serialize(r) // get the loaded object loaded := r.Serialize(r) // get the loaded object
for k, v := range change { // apply the changes, with a flatten method deepMerge(loaded, change)
loaded[k] = v
}
newObj := a.NewObj() newObj := a.NewObj()
b, err = json.Marshal(loaded) b, err = json.Marshal(loaded)
if err != nil { if err != nil {
@@ -255,6 +253,87 @@ func IsMySelf(peerID string, wfa Accessor) (bool, string) {
return peerID == pp.GetID(), pp.GetID() return peerID == pp.GetID(), pp.GetID()
} }
// deepMerge overlays patch values onto base, preserving base values for keys
// absent from patch, nil patch values, and empty strings when base is non-empty.
// This prevents partial frontend payloads from silently erasing server-managed
// fields (source, env, country, owners, creator_id, creation_date, …).
func deepMerge(base, patch map[string]interface{}) {
for k, pv := range patch {
bv := base[k]
switch pvTyped := pv.(type) {
case map[string]interface{}:
if bvMap, ok := bv.(map[string]interface{}); ok {
deepMerge(bvMap, pvTyped)
} else {
base[k] = pv
}
case []interface{}:
if bvSlice, ok := bv.([]interface{}); ok {
base[k] = mergeSlices(bvSlice, pvTyped)
} else {
base[k] = pv
}
case string:
// Don't overwrite a non-empty base value with an empty string.
if pvTyped != "" {
base[k] = pv
}
default:
if pv != nil {
base[k] = pv
}
}
}
}
// mergeSlices merges two slices element-wise.
// For slices of maps it matches elements by their "id" field when available;
// falls back to positional matching. An empty patch slice leaves base intact.
func mergeSlices(base, patch []interface{}) []interface{} {
if len(patch) == 0 {
return base
}
for _, e := range patch {
if _, ok := e.(map[string]interface{}); !ok {
return patch // non-map elements: replace wholesale
}
}
baseByID := map[string]map[string]interface{}{}
for _, e := range base {
if em, ok := e.(map[string]interface{}); ok {
if id, ok := em["id"].(string); ok && id != "" {
baseByID[id] = em
}
}
}
result := make([]interface{}, 0, len(patch))
for i, pe := range patch {
pm, _ := pe.(map[string]interface{})
if pm == nil {
result = append(result, pe)
continue
}
var baseElem map[string]interface{}
if id, ok := pm["id"].(string); ok && id != "" {
baseElem = baseByID[id]
}
if baseElem == nil && i < len(base) {
baseElem, _ = base[i].(map[string]interface{})
}
if baseElem != nil {
merged := make(map[string]interface{}, len(baseElem))
for k, v := range baseElem {
merged[k] = v
}
deepMerge(merged, pm)
result = append(result, merged)
} else {
result = append(result, pe)
}
}
return result
}
func GenerateNodeID() (string, error) { func GenerateNodeID() (string, error) {
folderStatic := "/var/lib/opencloud-node" folderStatic := "/var/lib/opencloud-node"
if _, err := os.Stat(folderStatic); err == nil { if _, err := os.Stat(folderStatic); err == nil {