歆萌 2019-11-03
我们的需求是为根据json每一个value
生成从root
到key
的path
(为了方便说明我们暂时不考虑数组的情况,只考虑object/number/bool/string)
举个例子,对于以下json字符串
{ "a": { "b":{ "c":{ "d0": "d0", "d1": "d1", "d2": "d2" } } } }
我们希望最终生成以下形式
a.b.c.d0 = d0 a.b.c.d1 = d1 a.b.c.d2 = d2
为此我们我们定义了以下结构
type Entry struct{ path []string val interface{} }
然后我们通过定义一个递归的函数来执行以下
func RecurseJson(jsonObj interface{}, path []string)([]*Entry, error){ switch jsonObj.(type){ case map[string]interface{}: //json object m := jsonObj.(map[string]interface{}) var ret []*Entry for k, v := range m{ newPath, err := RecurseJson(v, append(path, k)) // 递归 if err != nil { return nil, err } ret = append(ret, newPath...) } return ret, nil default: return []*Entry{ { path: path, val: jsonObj, }, }, nil } }
我们使用一个测试函数来测试执行结果
func TestRecurseJson(t *testing.T) { var obj interface{} err := json.Unmarshal([]byte(testJson), &obj) assert.NoError(t, err) ret, err := RecurseJson(obj, nil) assert.NoError(t, err) for _, entry := range ret{ fmt.Printf("%v \t = %v\n", strings.Join(entry.path, "."), entry.val) } }
输出的结果是
a.b.c.d2 = d0 a.b.c.d2 = d1 a.b.c.d2 = d2
而我们期望输出的结果是
a.b.c.d0 = d0 a.b.c.d1 = d1 a.b.c.d2 = d2
二者之间,第一行和第二行的path最末尾不一致,那么问题出在哪里呢?通过打点分析,发现在执行下面这一行的时候出现了问题
newPath, err := RecurseJson(v, append(path, k))
更细化一点,将他们拆分成两行
sub := append(path, k) newPath, err := RecurseJson(v, sub)
打点发现在第一行,也就是append的地方会遇到问题,append之后会将我们上一次append的结果覆盖掉,那么为什么会这样呢?通过打点可以发现,当我们递归路径进入到c
之后,也就是path
为["a", "b", "c"]
时,cap(path)
的值为4, len(path)
的值为3,每一次append所返回的newPath
,其实本质上是在切片中进行更改,newPath
和path
在底层指向的是同一片空间,只不过path
的长度是3,而newPath
长度是4,每一次append(path, k)
之后,修改的都是内存空间中的同一个位置。画图举例
因此正确的操作是将newPath := append(path, k)
改为
newPath := make([]string, len(path) copy(newPath, path) newPath := append(newPath, k)
需要分配给newPath一片新的空间,防止append在同一空间内不断更改。