// Get 从动态结构(如 map[string]interface{} 或 []interface{})中按路径获取值 // 如果 path 参数为空,则直接返回 v funcGet(v interface{}, path ...interface{})(interface{}, error) { // 遍历路径参数 path for i, el := range path { switch node := v.(type) { // 断言 v 的类型,注意每一轮循环中这个 v 都是新的值 // 情况 1:处理键为 string 的 map casemap[string]interface{}: key, ok := el.(string) // 路径元素必须是 string if !ok { returnnil, fmt.Errorf("expected string path element, got: %T (path element idx: %d)", el, i) } v, ok = node[key] // 从 map 中获取值 if !ok { returnnil, fmt.Errorf("missing key: %s (path element idx: %d)", key, i) }
// 情况 2:处理键为任意类型的 map casemap[interface{}]interface{}: var ok bool v, ok = node[el] // 直接使用路径元素作为键,来获取值 if !ok { returnnil, fmt.Errorf("missing key: %v (path element idx: %d)", el, i) }
// 情况 3:处理切片 case []interface{}: idx, ok := el.(int) // 路径元素必须是 int if !ok { returnnil, fmt.Errorf("expected int path element, got: %T (path element idx: %d)", el, i) } if idx < 0 || idx >= len(node) { // 索引越界检查 returnnil, fmt.Errorf("index out of range: %d (path element idx: %d)", idx, i) } v = node[idx] // 从切片中获取值
// 情况 4:不支持的类型 default: returnnil, fmt.Errorf("expected map or slice node, got: %T (path element idx: %d)", node, i) } }
// 返回最终 v 的值 return v, nil }
这里源码整体结构还是比较清晰的,首先是一个 for 循环,用来遍历 path(即我们传进来的 "object", "array", 2, "integer" 这些参数)。接着在 for 循环内部,根据 v(即我们传进来的 y 对象)的类型,分 4 种情况来获取元素。
// GetInt returns an int value denoted by the path. // // If path is empty or nil, v is returned as an int. funcGetInt(v interface{}, path ...interface{})(int, error) { v, err := Get(v, path...) if err != nil { return0, err } i, ok := v.(int) if !ok { return0, fmt.Errorf("expected int value, got: %T", v) } return i, nil }
根据源码可以发现,GetInt 内部调用了 Get 函数来获取值,只不过之后对值 v 进行了类型断言,判断其是否为 Int 类型,其他并无特殊功能。
所以我们只需要学习一个 Get 函数,然后记住其他所有 Get<Type> 函数都是基于 Get 函数来实现的即可。
// GetInteger returns an int64 value denoted by the path. // // This function accepts many different types and converts them to int64, namely: // // -integer types (int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64) // (which implies the aliases byte and rune too) // -floating point types (float64, float32) // -string (fmt.Sscan() will be used for parsing) // -any type with an Int64() (int64, error) method (e.g. json.Number) // // If path is empty or nil, v is returned as an int64. funcGetInteger(v interface{}, path ...interface{})(int64, error) { v, err := Get(v, path...) if err != nil { return0, err }
switch i := v.(type) { caseint64: return i, nil caseint: returnint64(i), nil caseint32: returnint64(i), nil caseint16: returnint64(i), nil caseint8: returnint64(i), nil caseuint: returnint64(i), nil caseuint64: returnint64(i), nil caseuint32: returnint64(i), nil caseuint16: returnint64(i), nil caseuint8: returnint64(i), nil casefloat64: returnint64(i), nil casefloat32: returnint64(i), nil casestring: var n int64 _, err := fmt.Sscan(i, &n) return n, err caseinterface { Int64() (int64, error) }: return i.Int64() default: return0, fmt.Errorf("expected some form of integer number, got: %T", v) } }
GetInteger 内部依然调用了 Get 函数来获取值,而这一次,其内部会将所有数字类型全部转换成 int64 类型来返回。
// Set 通过路径在动态结构(嵌套的 map 或 slice)中修改指定位置元素的值 funcSet(v interface{}, value interface{}, path ...interface{})error { // 1. 路径非空校验 iflen(path) == 0 { return fmt.Errorf("path cannot be empty") }
// 2. 分离末位元素 i := len(path) - 1// The last index iflen(path) > 1 { var err error v, err = Get(v, path[:i]...) // 末位元素的值 if err != nil { return err } }
el := path[i] // 末位元素的键或索引
// 3. 根据末位元素值的类型进行赋值 switch node := v.(type) { casemap[string]interface{}: // 情况 1:处理键为 string 的 map key, ok := el.(string) if !ok { return fmt.Errorf("expected string path element, got: %T (path element idx: %d)", el, i) } node[key] = value
casemap[interface{}]interface{}: // 情况 2:处理键为任意类型的 map node[el] = value
case []interface{}: // 情况 3:处理切片 idx, ok := el.(int) if !ok { return fmt.Errorf("expected int path element, got: %T (path element idx: %d)", el, i) } if idx < 0 || idx >= len(node) { return fmt.Errorf("index out of range: %d (path element idx: %d)", idx, i) } node[idx] = value
default: // 情况 4:不支持的类型 return fmt.Errorf("expected map or slice node, got: %T (path element idx: %d)", node, i) }
returnnil }
整体上看,Set 函数与 Get 函数要考虑的几种情况相同,因为它们都支持的是同一种类型。并且 Set 函数也需要遍历 path 从外向内逐层获取对象的值,拿到嵌套在最内层对象的值,然后对其进行修改。所以 Set 函数先做的操作就是通过调用 Get 函数拿到末位元素的值,然后根据末位元素值的类型对其进行修改。
// Delete 根据给定的 path,从 map 中删除指定的键值对,或从 slice 中删除指定元素 funcDelete(v interface{}, key interface{}, path ...interface{})error { // 1. 路径校验:若 v 是切片,路径不能为空 iflen(path) == 0 { if _, ok := v.([]interface{}); ok { return fmt.Errorf("path cannot be empty if v is a slice") } }
// ConvertMapI2MapS walks the given dynamic object recursively, and // converts maps with interface{} key type to maps with string key type. // This function comes handy if you want to marshal a dynamic object into // JSON where maps with interface{} key type are not allowed. // // Recursion is implemented into values of the following types: // -map[interface{}]interface{} // -map[string]interface{} // -[]interface{} // // When converting map[interface{}]interface{} to map[string]interface{}, // fmt.Sprint() with default formatting is used to convert the key to a string key. funcConvertMapI2MapS(v interface{})interface{} { switch x := v.(type) { casemap[interface{}]interface{}: // 目标转换类型,需要把 key 为 interface{} 类型的转换成 string m := map[string]interface{}{} for k, v2 := range x { switch k2 := k.(type) { casestring: // 如果 key 已经是 string 类型,则直接使用 m[k2] = ConvertMapI2MapS(v2) default: // 如果 key 是其他类型则需要转换成 string m[fmt.Sprint(k)] = ConvertMapI2MapS(v2) } } v = m
case []interface{}: // 递归处理数组元素 for i, v2 := range x { x[i] = ConvertMapI2MapS(v2) }
casemap[string]interface{}: // key 已经是 string,仅递归处理 value for k, v2 := range x { x[k] = ConvertMapI2MapS(v2) } }