V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
Seves
V2EX  ›  Go 编程语言

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

  •  
  •   Seves · 12 天前 · 1090 次点击

    本人算法较弱,问了各种 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"
      }
    ]
    
    11 条回复    2024-06-07 11:35:23 +08:00
    Ayanokouji
        1
    Ayanokouji  
       12 天前   ❤️ 1
    虽然和你的需求不匹配,但是还是可以参考下这个 https://github.com/oliveagle/jsonpath
    whoami9426
        2
    whoami9426  
       12 天前   ❤️ 1
    json-path, 1 楼已经给出答案了,自己再组装一下 '路径'->'值'
    MoYi123
        3
    MoYi123  
       12 天前   ❤️ 1
    要是其他语言我就直接给你写了, 但是 go 要写一堆反射.
    loveuer
        4
    loveuer  
       12 天前   ❤️ 1
    ```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
        5
    loveuer  
       12 天前
    table 是这个库,如果你想跑一下看看的话 "github.com/jedib0t/go-pretty/v6/table"
    Seves
        6
    Seves  
    OP
       12 天前
    @MoYi123 JavaScript/Python/Java/C 的也可以
    Projection
        7
    Projection  
       12 天前   ❤️ 1
    原始的 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
        8
    Ashe007  
       11 天前 via iPhone   ❤️ 1
    Java 中使用 Jackson 采取递归思路,可以处理任意 JSON 结构的扁平化解析,限定递归深度即可
    Seves
        9
    Seves  
    OP
       11 天前
    @Projection 非常感谢,我研究一下
    Seves
        10
    Seves  
    OP
       11 天前
    @Projection 再次感谢爹,基本解决了我的问题。https://github.com/iTanken/ExampleFlattenJSON.git
    Projection
        11
    Projection  
       11 天前   ❤️ 1
    @Seves 别这样,正常交流而已
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2906 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 13:36 · PVG 21:36 · LAX 06:36 · JFK 09:36
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.