package values import ( "encoding/json" "reflect" "strconv" "strings" "github.com/gogf/gf/v2/util/gconv" ) // UnwrapSchemaWrapper 递归剥掉 json-schema-editor 输出的 {type, value/attrs} 包裹层, // 只保留干净的 key/value 嵌套结构。 // 示例: // // {"a": {"type":"string","value":"hi"}} → {"a": "hi"} // {"b": {"type":"object","attrs":{"c":1}}} → {"b": {"c": 1}} // {"arr": {"type":"array","attrs":[{"type":"number","value":1}]}} → {"arr": [1]} func UnwrapSchemaWrapper(v any) any { switch val := v.(type) { case map[string]any: // 识别包裹节点:{type: "", value/attrs: <实际值>, ...} if t, ok := val["type"].(string); ok && isSchemaEditorType(t) { dataKey := "value" if t == "object" || t == "array" { dataKey = "attrs" } if raw, has := val[dataKey]; has { return UnwrapSchemaWrapper(raw) } } res := make(map[string]any, len(val)) for k, child := range val { res[k] = UnwrapSchemaWrapper(child) } return res case []any: res := make([]any, len(val)) for i, item := range val { res[i] = UnwrapSchemaWrapper(item) } return res default: return val } } // isSchemaEditorType 是否为 json-schema-editor 的 6 种类型标识 func isSchemaEditorType(t string) bool { switch t { case "string", "number", "boolean", "null", "object", "array": return true } return false } // MapResultByTemplate 按 template 定义的结构,从 source 中拷贝对应字段的值。 // 只保留 template 里出现的字段:对象字段按同名字段递归拷贝,数组字段按模板元素结构逐元素过滤,标量字段直接拷贝 source 的值。 func MapResultByTemplate(template map[string]any, source map[string]any) map[string]any { result := make(map[string]any, len(template)) for key, tmplVal := range template { srcVal, ok := source[key] if !ok { continue } if tmplMap, isMap := tmplVal.(map[string]any); isMap { if srcMap, isMap := srcVal.(map[string]any); isMap { result[key] = MapResultByTemplate(tmplMap, srcMap) } continue } if tmplArr, isArr := tmplVal.([]any); isArr { result[key] = mapTemplateArray(tmplArr, srcVal) continue } result[key] = srcVal } return result } // mapTemplateArray 按模板数组的元素结构映射 source 数组: // 模板首元素为对象时,逐元素按 MapResultByTemplate 过滤只保留模板字段; // 模板数组为空或首元素非对象(无法确定元素结构)时,原样拷贝 source 数组。 func mapTemplateArray(tmplArr []any, srcVal any) any { srcList, ok := srcVal.([]any) if !ok || len(tmplArr) == 0 { return srcVal } elemTmpl, ok := tmplArr[0].(map[string]any) if !ok { return srcVal } result := make([]any, 0, len(srcList)) for _, srcElem := range srcList { if srcMap, isMap := srcElem.(map[string]any); isMap { result = append(result, MapResultByTemplate(elemTmpl, srcMap)) } else { result = append(result, srcElem) } } return result } // assignBySchemaType 按当前字段声明的 schema 类型把值类型化: // string 遇数组/对象转 JSON 字符串;number/boolean 解析字符串;object/array 解析 JSON 字符串;其余原样返回 func assignBySchemaType(node map[string]interface{}, value any) any { t, _ := node["type"].(string) return assignByType(t, value) } // assignByType 按字段声明的 type 把值类型化;walkMap 的 schema 节点与 parseMap 的模型参数共用 func assignByType(t string, value any) any { switch t { case "string": return toSchemaString(value) case "number": return toSchemaNumber(value) case "boolean": return toSchemaBool(value) case "object", "array": return toSchemaStruct(value) default: return value } } // toSchemaString 转 string:字符串原样,数组元素拼成字符串(单元素取元素本身,多元素逗号连接),对象序列化为 JSON 字符串 func toSchemaString(v any) any { switch val := v.(type) { case []interface{}: parts := make([]string, 0, len(val)) for _, item := range val { parts = append(parts, toPlainString(item)) } return strings.Join(parts, ",") case map[string]interface{}: if b, err := json.Marshal(val); err == nil { return string(b) } } return v } // toPlainString 把数组元素转成不带括号的纯字符串: // 数组([]any / 类型化切片)逐元素取纯字符串,单元素取元素本身,多元素逗号连接; // 对象序列化为 JSON 字符串;其余原样字符串化。 func toPlainString(v any) string { if s, ok := v.(string); ok { return s } switch val := v.(type) { case []interface{}: parts := make([]string, 0, len(val)) for _, item := range val { parts = append(parts, toPlainString(item)) } return strings.Join(parts, ",") case map[string]interface{}: if b, err := json.Marshal(val); err == nil { return string(b) } } rv := reflect.ValueOf(v) if rv.IsValid() && (rv.Kind() == reflect.Slice || rv.Kind() == reflect.Array) { parts := make([]string, 0, rv.Len()) for i := 0; i < rv.Len(); i++ { parts = append(parts, toPlainString(rv.Index(i).Interface())) } return strings.Join(parts, ",") } if b, err := json.Marshal(v); err == nil { return string(b) } return gconv.String(v) } // toSchemaNumber 转 number:数字原样,字符串尝试解析为 float64,失败原样返回 func toSchemaNumber(v any) any { if s, ok := v.(string); ok { if f, err := strconv.ParseFloat(s, 64); err == nil { return f } } return v } // toSchemaBool 转 boolean:布尔原样,字符串尝试解析为 bool,失败原样返回 func toSchemaBool(v any) any { if s, ok := v.(string); ok { if b, err := strconv.ParseBool(s); err == nil { return b } } return v } // toSchemaStruct 转 object/array:合法 JSON 字符串解析为结构化数据,否则原样返回 func toSchemaStruct(v any) any { s, ok := v.(string) if !ok { return v } if !json.Valid([]byte(s)) { return v } var out any if err := json.Unmarshal([]byte(s), &out); err != nil { return v } return out } // schemaValueEmpty 值是否为空;0/false 视为有效值不剔除 func schemaValueEmpty(v interface{}) bool { switch val := v.(type) { case nil: return true case string: return val == "" case []interface{}: return len(val) == 0 case map[string]interface{}: return len(val) == 0 default: return false } }