求助各位爹:使用 Go 转换树形 JSON 数据平铺为 []map[string]any 单层数据列表格式

20 天前
 Seves

本人算法较弱,问了各种 GPT ,始终不能得到期望的结果,特来求助各位爹。需要将一个树形的 JSON 数据,从每一个子节点的最后一级节点向上级节点平铺,转换为单层数据列表,以下是相关 原数据目标字段 JSONPath期望得到的结果

原数据

{
    "mainData": [
        {
            "id": 1,
            "name:": "a",
            "sub": [
                {
                    "title": "A",
                    "used": true
                },
                {
                    "title": "B",
                    "used": false
                }
            ]
        },
        {
            "id": 2,
            "name:": "c",
            "sub": [
                {
                    "title": "C",
                    "used": true
                },
                {
                    "title": "D",
                    "used": false,
                    "s_data": [
                        {
                            "s_data_id": 4,
                            "s_data_value": 0.1
                        },
                        {
                            "s_data_id": 5,
                            "s_data_value": 0.2
                        }
                    ]
                }
            ]
        }
    ],
    "moreData": [
        {
            "m_id": 3,
            "m_name:": "e",
            "sub": [
                {
                    "s_title": "E",
                    "s_used": true,
                    "ext": {
                        "cid": "3_1",
                        "remark": "complex"
                    }
                },
                {
                    "s_title": "F",
                    "s_used": false,
                    "ext": {
                        "cid": "3_2",
                        "remark": "complex"
                    },
                    "children": [
                        {
                            "c_c_id": "3_2_1",
                            "c_c_title": "321"
                        },
                        {
                            "c_c_id": "3_2_2",
                            "c_c_title": "322"
                        }
                    ]
                }
            ]
        }
    ]
}

目标字段 JSONPath 列表

[
    "$.mainData[*].id",
    "$.mainData[*].name",
    "$.mainData[*].sub[*].title",
    "$.mainData[*].sub[*].used",
    "$.mainData[*].sub[*].s_data[*].s_data_id",
    "$.mainData[*].sub[*].s_data[*].s_data_value",
    "$.moreData[*].m_id",
    "$.moreData[*].m_name",
    "$.moreData[*].sub[*].s_title",
    "$.moreData[*].sub[*].s_used",
    "$.moreData[*].sub[*].ext.cid",
    "$.moreData[*].sub[*].ext.remark",
    "$.moreData[*].sub[*].children[*].c_c_id",
    "$.moreData[*].sub[*].children[*].c_c_title"
]

期望得到的结果


[
  {
    "$.mainData[*].id": 1,
    "$.mainData[*].name": "a",
    "$.mainData[*].sub[*].title": "A",
    "$.mainData[*].sub[*].used": true
  },
  {
    "$.mainData[*].id": 1,
    "$.mainData[*].name": "a",
    "$.mainData[*].sub[*].title": "B",
    "$.mainData[*].sub[*].used": false
  },
  {
    "$.mainData[*].id": 2,
    "$.mainData[*].name": "c",
    "$.mainData[*].sub[*].title": "C",
    "$.mainData[*].sub[*].used": true
  },
  {
    "$.mainData[*].id": 2,
    "$.mainData[*].name": "c",
    "$.mainData[*].sub[*].title": "D",
    "$.mainData[*].sub[*].used": false,
    "$.mainData[*].sub[*].s_data[*].s_data_id": 4,
    "$.mainData[*].sub[*].s_data[*].s_data_value": 0.1
  },
  {
    "$.mainData[*].id": 2,
    "$.mainData[*].name": "c",
    "$.mainData[*].sub[*].title": "D",
    "$.mainData[*].sub[*].used": false,
    "$.mainData[*].sub[*].s_data[*].s_data_id": 5,
    "$.mainData[*].sub[*].s_data[*].s_data_value": 0.2
  },
  {
    "$.moreData[*].m_id": 3,
    "$.moreData[*].m_name": "e",
    "$.moreData[*].sub[*].s_title": "E",
    "$.moreData[*].sub[*].s_used": true,
    "$.moreData[*].sub[*].ext.cid": "3_1",
    "$.moreData[*].sub[*].ext.remark": "complex"
  },
  {
    "$.moreData[*].m_id": 3,
    "$.moreData[*].m_name": "e",
    "$.moreData[*].sub[*].s_title": "F",
    "$.moreData[*].sub[*].s_used": false,
    "$.moreData[*].sub[*].ext.cid": "3_2",
    "$.moreData[*].sub[*].ext.remark": "complex",
    "$.moreData[*].sub[*].children[*].c_c_id": "3_2_1",
    "$.moreData[*].sub[*].children[*].c_c_title": "321"
  },
  {
    "$.moreData[*].m_id": 3,
    "$.moreData[*].m_name": "e",
    "$.moreData[*].sub[*].s_title": "F",
    "$.moreData[*].sub[*].s_used": false,
    "$.moreData[*].sub[*].ext.cid": "3_2",
    "$.moreData[*].sub[*].ext.remark": "complex",
    "$.moreData[*].sub[*].children[*].c_c_id": "3_2_2",
    "$.moreData[*].sub[*].children[*].c_c_title": "322"
  }
]
1213 次点击
所在节点    Go 编程语言
13 条回复
Ayanokouji
20 天前
虽然和你的需求不匹配,但是还是可以参考下这个 https://github.com/oliveagle/jsonpath
whoami9426
20 天前
json-path, 1 楼已经给出答案了,自己再组装一下 '路径'->'值'
MoYi123
20 天前
要是其他语言我就直接给你写了, 但是 go 要写一堆反射.
loveuer
20 天前
```go

func TablePrinter(data any, writers ...io.Writer) {
var w io.Writer = os.Stdout
if len(writers) > 0 && writers[0] != nil {
w = writers[0]
}

t := table.NewWriter()
structPrinter(t, "", data)
_, _ = fmt.Fprintln(w, t.Render())
}

func structPrinter(w table.Writer, prefix string, item any) {
Start:
rv := reflect.ValueOf(item)
if rv.IsZero() {
return
}

for rv.Type().Kind() == reflect.Pointer {
rv = rv.Elem()
}

switch rv.Type().Kind() {
case reflect.Invalid,
reflect.Uintptr,
reflect.Chan,
reflect.Func,
reflect.UnsafePointer:
case reflect.Bool,
reflect.Int,
reflect.Int8,
reflect.Int16,
reflect.Int32,
reflect.Int64,
reflect.Uint,
reflect.Uint8,
reflect.Uint16,
reflect.Uint32,
reflect.Uint64,
reflect.Float32,
reflect.Float64,
reflect.Complex64,
reflect.Complex128,
reflect.Interface,
reflect.String:
w.AppendRow(table.Row{strings.TrimPrefix(prefix, "."), rv.Interface()})
case reflect.Array, reflect.Slice:
for i := 0; i < rv.Len(); i++ {
p := strings.Join([]string{prefix, fmt.Sprintf("[%d]", i)}, ".")
structPrinter(w, p, rv.Index(i).Interface())
}
case reflect.Map:
for _, k := range rv.MapKeys() {
structPrinter(w, fmt.Sprintf("%s.{%v}", prefix, k), rv.MapIndex(k).Interface())
}
case reflect.Pointer:
goto Start
case reflect.Struct:
for i := 0; i < rv.NumField(); i++ {
p := fmt.Sprintf("%s.%s", prefix, rv.Type().Field(i).Name)
field := rv.Field(i)

//log.Debug("TablePrinter: prefix: %s, field: %v", p, rv.Field(i))

if !field.CanInterface() {
return
}

structPrinter(w, p, field.Interface())
}
}
}

func TableMapPrinter(data []byte) {
m := make(map[string]any)
if err := json.Unmarshal(data, &m); err != nil {
log.Warn(err.Error())
return
}

t := table.NewWriter()
addRow(t, "", m)
fmt.Println(t.Render())
}

func addRow(w table.Writer, prefix string, m any) {
rv := reflect.ValueOf(m)
switch rv.Type().Kind() {
case reflect.Map:
for _, k := range rv.MapKeys() {
key := k.String()
if prefix != "" {
key = strings.Join([]string{prefix, k.String()}, ".")
}
addRow(w, key, rv.MapIndex(k).Interface())
}
case reflect.Slice, reflect.Array:
for i := 0; i < rv.Len(); i++ {
addRow(w, fmt.Sprintf("%s[%d]", prefix, i), rv.Index(i).Interface())
}
default:
w.AppendRow(table.Row{prefix, m})
}
}
```

感觉你这个可以参考这个,我主要是用来打印程序启动时候的配置 struct 的
loveuer
20 天前
table 是这个库,如果你想跑一下看看的话 "github.com/jedib0t/go-pretty/v6/table"
Seves
20 天前
@MoYi123 JavaScript/Python/Java/C 的也可以
Projection
20 天前
原始的 JSON 数据中有几个 typo 需要修正回来:
"name:" "m_name:"
由于我不太明确你的具体需求,只能根据你想要的输出结果来写(将空格替换回来):

```python
import json


def unwind(root):
␣␣␣␣def dfs(node, inherit=False):
␣␣␣␣␣␣␣␣state = states[-1]
␣␣␣␣␣␣␣␣if not inherit:
␣␣␣␣␣␣␣␣␣␣␣␣states.append(state := states[-1].copy())
␣␣␣␣␣␣␣␣for k, v in node.items():
␣␣␣␣␣␣␣␣␣␣␣␣if isinstance(v, list):
␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣continue
␣␣␣␣␣␣␣␣␣␣␣␣path.append(k)
␣␣␣␣␣␣␣␣␣␣␣␣if isinstance(v, dict):
␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣dfs(v, True)
␣␣␣␣␣␣␣␣␣␣␣␣else:
␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣state[".".join(path)] = v
␣␣␣␣␣␣␣␣␣␣␣␣path.pop()

␣␣␣␣␣␣␣␣is_parent = any(isinstance(v, list) for v in node.values())
␣␣␣␣␣␣␣␣if is_parent:
␣␣␣␣␣␣␣␣␣␣␣␣for k, v in node.items():
␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣if isinstance(v, list):
␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣for vv in v:
␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣path.append(k + "[*]")
␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣dfs(vv)
␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣path.pop()
␣␣␣␣␣␣␣␣elif not inherit:
␣␣␣␣␣␣␣␣␣␣␣␣result.append(state.copy())
␣␣␣␣␣␣␣␣if not inherit:
␣␣␣␣␣␣␣␣␣␣␣␣states.pop()

␣␣␣␣path = ["$"]
␣␣␣␣states = [{}]
␣␣␣␣result = []
␣␣␣␣dfs(root, True)
␣␣␣␣return result


def main():
␣␣␣␣with open("a.json", "rb") as f:
␣␣␣␣␣␣␣␣data = json.load(f)
␣␣␣␣print(json.dumps(unwind(data), indent=2))


if __name__ == "__main__":
␣␣␣␣main()
```
Ashe007
19 天前
Java 中使用 Jackson 采取递归思路,可以处理任意 JSON 结构的扁平化解析,限定递归深度即可
Seves
19 天前
@Projection 非常感谢,我研究一下
Seves
19 天前
@Projection 再次感谢爹,基本解决了我的问题。https://github.com/iTanken/ExampleFlattenJSON.git
Projection
19 天前
@Seves 别这样,正常交流而已
jianyestudy
2 天前
被楼主的这个"爹"整乐了
Seves
2 天前

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1047380

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX