如何将数组转换为分层数组

5
我有一些数据,它是:
var currentData = [
    {'ticket':'CAP', 'child':'CT-1'},
    {'ticket':'CAP', 'child':'CT-2'},
    {'ticket':'CT-1', 'child':'CT-1-A'},
    {'ticket':'CT-1', 'child':'CT-1-B'}
];

数据是平面的,我需要将其转换为类似这样的形式:
{
    'ticket': 'CAP',
    children : [{
        'ticket' : 'CT-1',
        'children' : [{
            'ticket' : 'CT-1-A',
            'children' : []
        }, {
            'ticket' : 'CT-1-B',
            'children' : []
        }],
        [{
            'ticket' : 'CT-2',
            'children' : []
        }]
    }]
}

“我认为上面是有效的”?
“我非常迷茫,不知道如何做。我会展示我的努力,但我不确定我的方法是否正确。”

var currentData = [{'ticket':'cap', 'child':'CT-1'},{'ticket':'cap', 'child':'CT-2'}, {'ticket':'CT-1', 'child':'CT-1-A'},{'ticket':'CT-1', 'child':'CT-1-B'}];

var newList = [];
function convert(list){
    if (newList.length <= 0){
        var child = [];
        var emptyChild = [];
        child.push({'ticket': list[0].child, 'child': emptyChild });
        newList.push({'ticket': list[0].ticket, 'children' : child});
        list.splice(0,1);
    } // the if statement above works fine
    
    for(var i = 0;  i < list.length; i++) {
        var ticket = list[i].ticket;
        for(var j = 0; j < newList.length; j++) {
            if (newList[j].ticket == ticket){
                var child;
                var emptyChild = [];
                child = {'ticket': list[i].child, 'child': emptyChild };
                newList[j].children.push(child);
                list.splice(i,1);
                break;
            } // the if above works
            else{
                var child2 = getFromChildren(ticket, newList, list[i]); // child2 is Always null, even if getFromChildren returns an object
                newList[j].children.push(child2);
                list.splice(i,1);
                break;
            }
        }
    }   
    
    if (list.length > 0){
        convert(list);
    }
}

function getFromChildren(ticket, list, itemToAdd){

    if (list == null || list[0].children == null)
        return;
    
    for(var i = 0; i < list.length; i++) {
        if (list[i] == null)
        return;
        
        if (list[i].ticket == ticket){
            list[i].child.push(itemToAdd.child); // ** can't do this, javascript passes by value, not by reference :(
        } else{
            getFromChildren(ticket, list[i].children, itemToAdd);
        }
    }
}

convert(currentData);

我想我把它搞砸了。在评论中,我放了一个**来解释由于JavaScript不能通过引用传递而导致它无法工作,但是经过进一步阅读,我认为这不正确,因为我正在传递的是对象,而对象是按引用传递的? < p > < em >编辑

很遗憾,用currentData显示的数据也不总是从根开始


1
为什么一个没有子元素的对象其children属性被设置为null,而另一个被设置为空数组[] - ibrahim mahrir
你想要将数据转换成的语法不正确,我猜你忘记在结尾处添加 ]} 了吧? - doubleOrt
1
它是否总是从根节点开始? - Koushik Chatterjee
@KoushikChatterjee,不幸的是它不会。我已经更新了我的问题。 - MyDaftQuestions
错误仍然存在,这是您想让数据看起来像这样的吗:var obj = { 'ticket': 'CAP', children : [{ 'ticket' : 'CT-1', children : [{ 'ticket' : 'CT-1-A', 'children' : [] }, { 'ticket' : 'CT-1-B', 'children' : [] }] }, { 'ticket' : 'CT-2', children : [] } ] }; - doubleOrt
显示剩余3条评论
5个回答

2

function convert(arr) {
  var children = {};                                         // this object will hold a reference to all children arrays

  var res = arr.reduce(function(res, o) {                    // for each object o in the array arr
    if(!res[o.ticket]) {                                     // if there is no object for the element o.ticket
      res[o.ticket] = {ticket: o.ticket, children: []};      // then creates an object for it
      children[o.ticket] = res[o.ticket].children;           // and store a reference to its children array
    }
    if(!res[o.child]) {                                      // if there is no object for the element o.child
      res[o.child] = {ticket: o.child, children: []};        // then creates an object for it
      children[o.child] = res[o.child].children;             // and store a reference to its children array
    }
    return res;
  }, {});
  
  arr.forEach(function(o) {                                  // now for each object o in the array arr
    children[o.ticket].push(res[o.child]);                   // add the object of o.child (from res) to its children array
    delete res[o.child];                                     // and remove the child object from the object res
  });
  
  return res;
}



var currentData = [
    {'ticket':'CAP', 'child':'CT-1'},
    {'ticket':'CAP', 'child':'CT-2'},
    {'ticket':'CT-1', 'child':'CT-1-A'},
    {'ticket':'CT-1', 'child':'CT-1-B'}
];

console.log(convert(currentData));

说明:

reduce 方法为每个元素(包括子元素)创建一个对象,形式为:{ ticket: "...", children: [] }。因此,在 reduce 方法之后,res 对象将如下所示:

res = {
    'CAP': { ticket: 'CAP', children: [] },
    'CT-1': { ticket: 'CT-1', children: [] },
    'CT-2': { ticket: 'CT-2', children: [] },
    'CT-1-A': { ticket: 'CT-1-A', children: [] },
    'CT-1-B': { ticket: 'CT-1-B', children: [] },
}

现在我们来看一下forEach部分,它会再次循环数组,对于每个对象,它会从上面的res中获取.child对象,并将其推入.ticket对象的children属性中(该属性的引用存储在children对象中),然后从res对象中移除.child对象。请注意保留HTML标签,不做任何解释。

我需要学习/阅读reduce函数。这段代码与我的方法非常不同,但我觉得需要一点时间来理解差异。感谢您花费时间。 - MyDaftQuestions
不用谢!我已经添加了我的方法的简要说明。这里是关于 reduceMDN文档链接。 - ibrahim mahrir

1
以下使用reduce将数据分组到Map中,然后将数据转换为像上面展示的对象。您需要一个现代浏览器来运行下面的代码段,或者使用Babeljs之类的转译器将其转换为es5语法。

let currentData = [
    {'ticket':'CAP', 'child':'CT-1'},
    {'ticket':'CAP', 'child':'CT-2'},
    {'ticket':'CT-1', 'child':'CT-1-A'},
    {'ticket':'CT-1', 'child':'CT-1-B'}
];

let children = currentData.map(e => e.child);
currentData.sort((a,b) => children.indexOf(a.ticket));

let res = currentData.reduce((a,b) => {
    if (! children.includes(b.ticket)) {
        return a.set(b.ticket, (a.get(b.ticket) || [])
            .concat({ticket: b.child,
                children: currentData
                    .filter(el => el.ticket === b.child)
                    .map(el => ({ticket: el.child, children: []}))}))
    }
    return a;
}, new Map);

let r = {};

for (let [key,value] of res.entries()) {
    r.ticket = key;
    r.children = value;
}

console.log(r);


你可能需要更新你的浏览器。上面使用了一些ES6语法,不支持IE。@MyDaftQuestions - baao
我已经启用了Babel,你可以再试一次吗?@MyDaftQuestions - baao

1
使用递归的解决方案,起始节点可以更改。

var currentData = [{'ticket': 'cap','child': 'CT-1'}, {'ticket': 'cap','child': 'CT-2'}, {'ticket': 'CT-1','child': 'CT-1-A'}, {'ticket': 'CT-1','child': 'CT-1-B'}];

function convert(data, start){
  return {
    ticket: start,
    childs: data.filter(d => d.ticket == start)
                .reduce((curr, next) => curr.concat([next.child]), [])
                .map(c => convert(data, c))
  }
}

let result = convert(currentData, 'cap');

console.log(result);
.as-console-wrapper{top: 0; max-height: none!important;}


1
我会选择一个简单的 for 方法,就像这样:

var currentData = [
    {'ticket':'CAP', 'child':'CT-1'},
    {'ticket':'CAP', 'child':'CT-2'},
    {'ticket':'CT-1', 'child':'CT-1-A'},
    {'ticket':'CT-1', 'child':'CT-1-B'}
];
var leafs = {};
var roots = {};
var tickets = {};
for(var i=0; i<currentData.length; i++){
    var ticket = currentData[i].ticket;
    var child = currentData[i].child;
    if(!tickets[ticket]){
        tickets[ticket] = {ticket:ticket,children:[]};
        if(!leafs[ticket]){
            roots[ticket] = true;
        }
    }
    if(!tickets[child]){
        tickets[child] = {ticket:child,children:[]};
    }
    delete roots[child];
    leafs[child] = true;
    tickets[ticket].children.push(tickets[child]);
}
for(var ticket in roots){
    console.log(tickets[ticket]);
}


0

如果你对使用回调函数的 reducemapforEach 进行迭代不熟悉,那么我提出了一种方法,代码是平面的,在另一个映射对象中存储对象引用,并且只迭代一次源数组。

如果有什么理解困难的地方,请添加注释,我会进行说明;

var currentData = [
    {'ticket':'CT-1', 'child':'CT-1-A'},
    {'ticket':'CT-1', 'child':'CT-1-B'},
    {'ticket':'CAP', 'child':'CT-1'},
    {'ticket':'CAP', 'child':'CT-2'}
];
function buildHierarchy(flatArr) {
    let root = {},
        nonRoot = {},
        tempMap = {};
    Object.setPrototypeOf(root, nonRoot);
    for (let idx = 0; idx < flatArr.length; idx++) {
        let currTicket = flatArr[idx];
        let tempTicket = tempMap[currTicket.ticket] || {ticket: currTicket.ticket, children: []};
        tempMap[currTicket.ticket] = tempTicket;
        if (currTicket.child) {
            let tempChild = tempMap[currTicket.child] || {ticket: currTicket.child, children: []};
            tempTicket.children.push(tempChild);
            tempMap[currTicket.child] = tempChild;
            delete root[tempChild.ticket];
            nonRoot[tempChild.ticket] = true;
        }
        root[tempTicket.ticket] = true;

    }
    return tempMap[Object.keys(root)[0]];
}

console.log(buildHierarchy(currentData));

我已经改变了您源数组的顺序,以便将根对象放在任何位置,代码应该可以正常工作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接