package utils import "github.com/gogf/gf/v2/util/gconv" // MergeNode 按模板节点的元数据把用户数据节点合并为模板可解析的节点: // - 用户节点无 type 键 → 套上模板类型包装(标量取用户值,空则回落模板 value/defaultValue) // - 用户节点有 type 键 → 递归合并子节点(子节点缺 type 时同样补模板类型,避免模板包装泄漏) // - 对象:模板 attrs 缺省字段补进用户容器(保留模板默认值) // - 数组:用户 enumValues/attrs 元素逐个与模板元素原型合并(模板未提供的槽位克隆原型) func MergeNode(user, tmpl any) any { tmplMap, ok := tmpl.(map[string]any) if !ok { return user } fieldType, _ := tmplMap["type"].(string) // 用户节点已带 type:按用户类型递归合并子节点;标量叶子已完备,直接返回 if userMap, ok := user.(map[string]any); ok { if ut, isTpl := userMap["type"]; isTpl { switch gconv.String(ut) { case TypeObject: return mergeObject(userMap, tmplMap) case TypeArray: return mergeArray(userMap, tmplMap) default: return userMap } } } switch fieldType { case TypeString, TypeBool, TypeNumber, TypeNumberInt, TypeNumberFloat: return wrapScalarNode(tmplMap, user) case TypeObject: return mergeObject(user, tmplMap) case TypeArray: return mergeArray(user, tmplMap) default: return user } } // wrapScalarNode 把用户标量值包装为 {type, value} 节点;用户值为空时回落模板 value/defaultValue func wrapScalarNode(tmplMap map[string]any, user any) map[string]any { val := user if m, ok := user.(map[string]any); ok { if v, has := m["value"]; has { val = v } else { val = nil } } node := map[string]any{"type": normalizeScalarType(gconv.String(tmplMap["type"]))} if hasUsableValue(val) { node["value"] = val } else if v, has := tmplMap["value"]; has { node["value"] = v } else if d, has := tmplMap["defaultValue"]; has { node["value"] = d } return node } // normalizeScalarType integer/float 统一为 number(resolveField 只分发 string/boolean/number) func normalizeScalarType(t string) string { if t == TypeNumberInt || t == TypeNumberFloat { return TypeNumber } return t } // mergeObject 合并对象节点:模板 attrs 缺省字段补进用户容器(模板默认值保留) func mergeObject(user any, tmplMap map[string]any) map[string]any { tmplAttrs, _ := tmplMap["attrs"].(map[string]any) node := map[string]any{"type": TypeObject} if tmplAttrs == nil { node["attrs"] = user return node } var container map[string]any switch u := user.(type) { case map[string]any: if a, has := u["attrs"]; has { if am, ok := a.(map[string]any); ok { container = copyMap(am) } else { container = map[string]any{} } } else if v, has := u["value"]; has { if vm, ok := v.(map[string]any); ok { container = copyMap(vm) } else { container = map[string]any{} } } else { container = copyMap(u) } default: container = map[string]any{} } for k, subTmpl := range tmplAttrs { if _, has := container[k]; !has { container[k] = DeepCopyNode(subTmpl) } else { container[k] = MergeNode(container[k], subTmpl) } } node["attrs"] = container return node } // mergeArray 合并数组节点:用户 enumValues/attrs 元素逐个与模板元素原型合并。 // 用户未填的槽位(sjson null 填充)用模板对应槽位原型补位,保留下标不塌缩。 func mergeArray(user any, tmplMap map[string]any) map[string]any { proto := arrayElementTemplate(tmplMap) tmplSlots, _ := tmplMap["enumValues"].([]any) // 模板各槽位原型,按下标一一对应 node := map[string]any{"type": TypeArray} slotProto := func(i int) any { if i < len(tmplSlots) { return tmplSlots[i] } return proto } switch u := user.(type) { case map[string]any: if evs, ok := u["enumValues"].([]any); ok { out := make([]any, len(evs)) for i, ev := range evs { if ev == nil { // 保留 null 槽位以维持下标;解析阶段 resolveArray 会丢弃空元素, // 避免用模板原型填充 null 而物化出"幽灵"元素(用户未填的数组槽位) out[i] = nil continue } out[i] = mergeArrayElement(ev, slotProto(i)) } node["enumValues"] = out } else if a, ok := u["attrs"].([]any); ok { out := make([]any, len(a)) for i, item := range a { out[i] = mergeArrayElement(item, proto) } node["attrs"] = out } else if v, has := u["value"]; has { node["value"] = v } else if len(u) > 0 { node["attrs"] = []any{mergeArrayElement(u, proto)} } case []any: if isSchemaElementList(u) { out := make([]any, len(u)) for i, item := range u { out[i] = mergeArrayElement(item, proto) } node["attrs"] = out } else { node["value"] = u } default: node["value"] = user } return node } // mergeArrayElement 合并单个数组元素:元素缺 type 时按槽位原型补默认字段。 // slotProto 可为 nil(模板未提供槽位原型时)→ 原样返回用户元素 func mergeArrayElement(ev any, slotProto any) any { if ev == nil { return ev } proto, _ := slotProto.(map[string]any) if proto == nil { return ev } evMap, ok := ev.(map[string]any) if !ok { return ev } if _, isTpl := evMap["type"]; isTpl { return ev } // 元素原型自身是包装节点(嵌套对象/数组)→ 按模板类型合并 if t, has := proto["type"].(string); has && t != "" { return MergeNode(ev, proto) } protoAttrs, _ := proto["attrs"].(map[string]any) var container map[string]any if a, has := evMap["attrs"]; has { if am, ok := a.(map[string]any); ok { container = copyMap(am) } else { container = map[string]any{} } } else { container = copyMap(evMap) } for k, subTmpl := range protoAttrs { if _, has := container[k]; !has { container[k] = DeepCopyNode(subTmpl) } else { container[k] = MergeNode(container[k], subTmpl) } } return map[string]any{"attrs": container} } // isSchemaElementList 判断 []any 是否为 schema 元素列表(元素均为 map,且带 type 或 attrs 包装) func isSchemaElementList(list []any) bool { if len(list) == 0 { return false } for _, item := range list { m, ok := item.(map[string]any) if !ok { return false } if _, hasType := m["type"]; !hasType { if _, hasAttrs := m["attrs"]; !hasAttrs { return false } } } return true } // DeepCopyNode 深拷贝任意嵌套节点(模板节点整体拷贝进 src 时使用,避免共享引用被后续回填修改) func DeepCopyNode(v any) any { switch val := v.(type) { case map[string]any: out := make(map[string]any, len(val)) for k, sub := range val { out[k] = DeepCopyNode(sub) } return out case []any: out := make([]any, len(val)) for i, sub := range val { out[i] = DeepCopyNode(sub) } return out default: return v } } // hasUsableValue 值是否可写入节点 value;0/false 视为有效值 func hasUsableValue(v any) bool { switch val := v.(type) { case nil: return false case string: return val != "" case []any: return len(val) > 0 case map[string]any: return len(val) > 0 default: return true } }