在javascript中将平面json文件转换为树结构

时间:2018-04-15 19:06:50

标签: javascript algorithm d3.js recursion

我基本上是尝试将平面json文件转换为树视图。这里,树视图所需的父子关系由使用source和target的链接键进行。

以下是示例原始输入:

{
  "nodes" : [
    {
      name: "bz_db",
      index: 0
    },
    {
      name: "mysql",
      index: 1
    },
    {
      name: "postgres",
      index: 2
    },
    {
      name: "it-infra",
      index: 3
    },
    {
      name: "user-count",
      index: 4
    }
  ],
  links: [
    {
      source: 0, target: 1
    },
    {
      source: 0, target: 3
    },
    {
      source: 1, target: 3
    },
    {
      source: 3, target: 4
    }
  ]
}

正如您所看到的,链接字段维护了这种关系,最后我想要这种格式的数据:

{
  name: "bz_db",
  children: [
    {
      name: "mysql",
      children: [
        {
          name: "it-infra",
          children: [
            {
              name: "user_count",
              children: []
            }
          ]
        }
      ]
    },
    {
      name: "it-infra",
      children: [{
          name: "user_count",
          children: []
        }
      ]
    }
  ]
}

我试图解决这个问题,但它适用于1级(显示所选根元素的直接子级。

var findObjectByKeyValue = function(arrayOfObject, key, value){
                    return _.find(arrayOfObject, function(o){ return o[key] == value})
                }

var rootObject = findObjectByKeyValue(sample_raw_input.nodes, 'name', 'bz_db');
var treeObject = {
                        name: rootObject.name,
                        index: rootObject.index,
                        root: true,
                        children: []
                    };
angular.forEach(dependencyData.links, function(eachLink){
                        if(treeObject.index == eachLink.source){
                            var rawChildObject = findObjectByKeyValue(dependencyData.nodes, 'index', eachLink.target);
                            var childObject = {};
                            childObject.index = rawChildObject.index;
                            childObject.name = rawChildObject.name;
                            childObject.children = [];
                            treeObject.children.push(childObject);
                        }
                    });

但是上面的代码只返回了第一级的depndencies,但我想要层次关系。 我知道我可以在这里使用递归。但我对它不太满意。

3 个答案:

答案 0 :(得分:4)

Josh的回答使用了一系列map - > filter - > map - > find个调用,每个调用都通过收集数据。随着集合中节点数量的增加,这个循环循环循环会导致惊人的计算复杂度。

通过在每个reducenodes上使用单个links传递,您可以大大简化树的创建。与需要线性时间(较慢)的数组Map相比,find也可以在对数时间内执行查找。当您考虑为输入的每个元素调用此操作时,可以清楚地看到时间上的显着差异。

const makeTree = (nodes = [], links = []) =>
  links.reduce
    ( (t, l) =>
        t.set ( l.source
              , MutableNode.push ( t.get (l.source)
                                 , t.get (l.target)
                                 )
              )
    , nodes.reduce
        ( (t, n) => t.set (n.index, MutableNode (n.name))
        , new Map
        )
    )
    .get (0)

最后,我们提供了我们依赖的MutableNode界面

const MutableNode = (name, children = []) =>
  ({ name, children })

MutableNode.push = (node, child) =>
  (node.children.push (child), node)

以下是完整的程序演示。 JSON.stringify仅用于显示结果



const MutableNode = (name, children = []) =>
  ({ name, children })
  
MutableNode.push = (node, child) =>
  (node.children.push (child), node)

const makeTree = (nodes = [], links = []) =>
  links.reduce
    ( (t, l) =>
        t.set ( l.source
              , MutableNode.push ( t.get (l.source)
                                 , t.get (l.target)
                                 )
              )
    , nodes.reduce
        ( (t, n) => t.set (n.index, MutableNode (n.name))
        , new Map
        )
    )
    .get (0)
    
const data =
  { nodes:
      [ { name: "bz_db", index: 0 }
      , { name: "mysql", index: 1 }
      , { name: "postgres", index: 2 }
      , { name: "it-infra", index: 3 }
      , { name: "user-count", index: 4 }
      ]
  , links: 
      [ { source: 0, target: 1 }
      , { source: 0, target: 3 }
      , { source: 1, target: 3 }
      , { source: 3, target: 4 }
      ]
  }

const tree = 
  makeTree (data.nodes, data.links)

console.log (JSON.stringify (tree, null, 2))




答案 1 :(得分:1)

您可以依赖跟踪对象引用,并且无需任何递归即可执行此操作。使用Object.assign,将节点列表映射到其子节点:

// Assuming that input is in `input`
const nodes = input.nodes.reduce((a, node) => {
  a[node.index] = { ...node, index: undefined };
  return a;
}, []);

// organize the links by their source
const links = input.links.reduce((a, link) => {
  return a.set((a.get(link.source) || []).concat(nodes[link.target]);
}, new Map());

// Apply side effect of updating node children
nodes.forEach(node => Object.assign(node, {
  children: links.get(node.index),
}));

因此,我将获取节点列表,并为每个节点分配(以改变节点本身 - 请记住这是一个副作用)一个新数组。那些children是链接此节点的所有链接,我们Array#map将它们target ID转换为我们想要的实际节点。

答案 2 :(得分:0)

只分享样本,与您的样本略有不同。 但这给了您递归功能的提示。

jsFiddle flat array json transform to recursive tree json

            function getNestedChildren(arr, parent) {
            var out = []
            for(var i in arr) {
                if(arr[i].parent == parent) {
                    var children = getNestedChildren(arr, arr[i].id)

                    if(children.length) {
                        arr[i].children = children
                    }
                    out.push(arr[i])
                }
            }
            return out
        }


        var flat = [
            {id: 1, title: 'hello', parent: 0},
            {id: 2, title: 'hello', parent: 0},
            {id: 3, title: 'hello', parent: 1},
            {id: 4, title: 'hello', parent: 3},
            {id: 5, title: 'hello', parent: 4},
            {id: 6, title: 'hello', parent: 4},
            {id: 7, title: 'hello', parent: 3},
            {id: 8, title: 'hello', parent: 2}
        ]


        var nested = getNestedChildren(flat, 0)

        console.log(nested)