我遇到了一个问题,试图将平面对象数组转换为基于name属性的嵌套对象数组。
将input
数组转换为类似desiredOutput
数组的结构的最佳方法是什么?
var input = [
{
name: 'foo',
url: '/somewhere1',
templateUrl: 'foo.tpl.html',
title: 'title A',
subtitle: 'description A'
},
{
name: 'foo.bar',
url: '/somewhere2',
templateUrl: 'anotherpage.tpl.html',
title: 'title B',
subtitle: 'description B'
},
{
name: 'buzz.fizz',
url: '/another/place',
templateUrl: 'hello.tpl.html',
title: 'title C',
subtitle: 'description C'
},
{
name: 'foo.hello.world',
url: '/',
templateUrl: 'world.tpl.html',
title: 'title D',
subtitle: 'description D'
}
]
var desiredOutput = [
{
name: 'foo',
url: '/somewhere1',
templateUrl: 'foo.tpl.html',
data: {
title: 'title A',
subtitle: 'description A'
},
children: [
{
name: 'bar',
url: '/somewhere2',
templateUrl: 'anotherpage.tpl.html',
data: {
title: 'title B',
subtitle: 'description B'
}
},
{
name: 'hello',
data: {},
children: [
{
name: 'world',
url: '/',
templateUrl: 'world.tpl.html',
data: {
title: 'title D',
subtitle: 'description D'
}
}
]
}
]
},
{
name: 'buzz',
data: {},
children: [
{
name: 'fizz',
url: '/',
templateUrl: 'world.tpl.html',
data: {
title: 'title C',
subtitle: 'description C'
}
}
]
}
]
请注意,不保证输入数组中对象的顺序。 此代码将在Node.js环境中运行,我可以使用lodash等库来实现所需的输出。
非常感谢任何帮助。
答案 0 :(得分:1)
使用Lodash(因为你为什么要在没有实用程序库的情况下操作复杂数据)。这是the fiddle。
function formatRoute(route) {
return _.merge(_.pick(route, ['url', 'templateUrl']), {
name: route.name.split('.'),
data: _.pick(route, ['title', 'subtitle']),
children: []
});
}
function getNameLength(route) {
return route.name.length;
}
function buildTree(tree, route) {
var path = _.slice(route.name, 0, -1);
insertAtPath(tree, path, _.merge({}, route, {
name: _.last(route.name)
}));
return tree;
}
function insertAtPath(children, path, route) {
var head = _.first(path);
var match = _.find(children, function (child) {
return child.name === head;
});
if (path.length === 0) {
children.push(route);
}
else {
if (!match) {
match = {
name: head,
data: {},
children: []
};
children.push(match);
}
insertAtPath(match.children, _.rest(path), route);
}
}
// Map the routes into their correct formats.
var routes = _.sortBy(_.map(input, formatRoute), getNameLength);
// Now we can reduce this well formatted array into the desired format.
var out = _.reduce(routes, buildTree, []);
它通过重新整形初始输入来工作,以便将名称拆分为数组并添加data / children属性。然后它减少buildTree
上的数据,该数据使用变异函数(:()在给定路径的reduce中插入当前项。
奇怪的if (!match)
部分确保在未使用URL等在初始数据集中明确指定缺少的段时添加它们。
实际完成工作的最后两行应该是一个小函数,它可以用一些JSDoc。遗憾的是我没有让它完全递归,我依靠数组突变将路径对象插入到树的深处。
应该很简单,但是可以遵循。
答案 1 :(得分:0)
此解决方案仅使用本机JS方法。它可以肯定地进行优化,但我保留了原样,以便更容易跟进(或者我希望如此)。我还注意不要修改原始输入,因为JS通过引用传递对象。
var input = [{
name: 'foo',
url: '/somewhere1',
templateUrl: 'foo.tpl.html',
title: 'title A',
subtitle: 'description A'
}, {
name: 'foo.bar',
url: '/somewhere2',
templateUrl: 'anotherpage.tpl.html',
title: 'title B',
subtitle: 'description B'
}, {
name: 'buzz.fizz',
url: '/another/place',
templateUrl: 'hello.tpl.html',
title: 'title C',
subtitle: 'description C'
}, {
name: 'foo.hello.world',
url: '/',
templateUrl: 'world.tpl.html',
title: 'title D',
subtitle: 'description D'
}];
// Iterate over input array elements
var desiredOutput = input.reduce(function createOuput(arr, obj) {
var names = obj.name.split('.');
// Copy input element object as not to modify original input
var newObj = Object.keys(obj).filter(function skipName(key) {
return key !== 'name';
}).reduce(function copyObject(tempObj, key) {
if (key.match(/url$/i)) {
tempObj[key] = obj[key];
}
else {
tempObj.data[key] = obj[key];
}
return tempObj;
}, {name: names[names.length - 1], data: {}});
// Build new output array with possible recursion
buildArray(arr, names, newObj);
return arr;
}, []);
document.write('<pre>' + JSON.stringify(desiredOutput, null, 4) + '</pre>');
// Helper function to search array element objects by name property
function findIndexByName(arr, name) {
for (var i = 0, len = arr.length; i < len; i++) {
if (arr[i].name === name) {
return i;
}
}
return -1;
}
// Recursive function that builds output array
function buildArray(arr, paths, obj) {
var path = paths.shift();
var index = findIndexByName(arr, path);
if (paths.length) {
if (index === -1) {
arr.push({
name: path,
children: []
});
index = arr.length - 1;
}
if (!Array.isArray(arr[index].children)) {
arr[index].children = [];
}
buildArray(arr[index].children, paths, obj);
} else {
arr.push(obj);
}
return arr;
}
答案 2 :(得分:0)
这是我基于Lodash的尝试。
首先,我发现_.set
可以理解深层嵌套的对象表示法,因此我使用它来构建一个编码父子关系的树:
var tree = {};
input.forEach(o => _.set(tree, o.name, o));
这会产生:
{
"foo": {
"name": "foo",
"url": "/somewhere1",
"templateUrl": "foo.tpl.html",
"title": "title A",
"subtitle": "description A",
"bar": {
"name": "foo.bar",
"url": "/somewhere2",
"templateUrl": "anotherpage.tpl.html",
"title": "title B",
"subtitle": "description B"
},
"hello": {
"world": {
"name": "foo.hello.world",
"url": "/",
"templateUrl": "world.tpl.html",
"title": "title D",
"subtitle": "description D"
}
}
},
"buzz": {
"fizz": {
"name": "buzz.fizz",
"url": "/another/place",
"templateUrl": "hello.tpl.html",
"title": "title C",
"subtitle": "description C"
}
}
}
这实际上与所需的输出相距甚远。但是孩子们的名字与title
等其他属性一起显示为属性。
然后出现了编写递归函数的繁琐过程,该函数接受了这个中间树并以您希望的方式重新格式化:
children
属性数组。hello
中的foo.hello.world
之类的中间节点没有任何数据,所以它必须插入data: {}
和name
属性。 data
属性中的字幕,并清理仍然完全合格的name
个。代码:
var buildChildrenRecursively = function(tree) {
var children = _.keys(tree).filter(k => _.isObject(tree[k]));
if (children.length > 0) {
// Step 1 of reformatting: move children to children
var newtree = _.omit(tree, children);
newtree.children = children.map(k => buildChildrenRecursively(tree[k]));
// Step 2 of reformatting: deal with long chains with missing intermediates
children.forEach((k, i) => {
if (_.keys(newtree.children[i]).length === 1) {
newtree.children[i].data = {};
newtree.children[i].name = k;
}
});
// Step 3 of reformatting: move title/subtitle to data; keep last field in name
newtree.children = newtree.children.map(function(obj) {
if ('data' in obj) {
return obj;
}
var newobj = _.omit(obj, 'title,subtitle'.split(','));
newobj.data = _.pick(obj, 'title,subtitle'.split(','));
newobj.name = _.last(obj.name.split('.'));
return newobj;
});
return (newtree);
}
return tree;
};
var result = buildChildrenRecursively(tree).children;
输出:
[
{
"name": "foo",
"url": "/somewhere1",
"templateUrl": "foo.tpl.html",
"children": [
{
"name": "bar",
"url": "/somewhere2",
"templateUrl": "anotherpage.tpl.html",
"data": {
"title": "title B",
"subtitle": "description B"
}
},
{
"children": [
{
"name": "world",
"url": "/",
"templateUrl": "world.tpl.html",
"data": {
"title": "title D",
"subtitle": "description D"
}
}
],
"data": {},
"name": "hello"
}
],
"data": {
"title": "title A",
"subtitle": "description A"
}
},
{
"children": [
{
"name": "fizz",
"url": "/another/place",
"templateUrl": "hello.tpl.html",
"data": {
"title": "title C",
"subtitle": "description C"
}
}
],
"data": {},
"name": "buzz"
}
]
胜利者去战利品。
答案 3 :(得分:0)
此解决方案不使用递归,它使用指向对象图中上一项的引用指针。
请注意,此解决方案确实使用了lodash。这里的JSFiddle示例http://jsfiddle.net/xpb75dsn/1/
defer teardown()
答案 4 :(得分:0)
这是一个使用lodash的完全无递归的方法。当我想到_.set
和_.get
有多好时,我想到了,我意识到我可以替换对象&#34;路径&#34;序列为children
。
首先,构建一个对象/哈希表,其密钥等于name
数组的input
属性:
var names = _.object(_.pluck(input, 'name'));
// { foo: undefined, foo.bar: undefined, buzz.fizz: undefined, foo.hello.world: undefined }
(不要试图JSON.stringify
这个对象!因为它的值都是未定义的,所以它的计算结果为{}
...)
接下来,在每个元素上应用两个转换:(1)将标题和副标题清理为子属性data
和(2),这有点棘手,找到所有中间路径,如{{ 1}}和buzz
并未在foo.hello
中表示但其子女为。展平此数组数组,并按input
字段中.
的数量对其进行排序。
name
这段代码可能看起来令人生畏,但看看它输出的内容应该让你相信它非常简单:它只是一个包含原始var partial = _.flatten(
input.map(o =>
{
var newobj = _.omit(o, 'title,subtitle'.split(','));
newobj.data = _.pick(o, 'title,subtitle'.split(','));
return newobj;
})
.map(o => {
var parents = o.name.split('.').slice(0, -1);
var missing =
parents.map((val, idx) => parents.slice(0, idx + 1).join('.'))
.filter(name => !(name in names))
.map(name => {
return {
name,
data : {},
}
});
return missing.concat(o);
}));
partial = _.sortBy(partial, o => o.name.split('.').length);
的平面数组加上所有中间路径。在input
中,按input
中的点数排序,并为每个点添加新的name
字段。
data
我们几乎可以免费回家了。魔法的最后一点需要存储一些全局状态。我们将循环使用此平展[
{
"name": "foo",
"url": "/somewhere1",
"templateUrl": "foo.tpl.html",
"data": {
"title": "title A",
"subtitle": "description A"
}
},
{
"name": "buzz",
"data": {}
},
{
"name": "foo.bar",
"url": "/somewhere2",
"templateUrl": "anotherpage.tpl.html",
"data": {
"title": "title B",
"subtitle": "description B"
}
},
{
"name": "buzz.fizz",
"url": "/another/place",
"templateUrl": "hello.tpl.html",
"data": {
"title": "title C",
"subtitle": "description C"
}
},
{
"name": "foo.hello",
"data": {}
},
{
"name": "foo.hello.world",
"url": "/",
"templateUrl": "world.tpl.html",
"data": {
"title": "title D",
"subtitle": "description D"
}
}
]
数组,将partial
字段替换为name
和_.get
可以使用的包含_.set
的路径数字指数:
children
已映射到foo
children.0
至buzz
,children.1
至foo.bar
等当我们迭代地(不是递归地!)构建这个路径序列时,我们使用children.0.children.0
将_.set
的每个元素注入到它的适当位置。
代码:
partial
此对象/ hash var name2path = {'empty' : ''};
var out = {};
partial.forEach(obj => {
var split = obj.name.split('.');
var par = name2path[split.slice(0, -1).join('.') || "empty"];
var path = par + 'children.' + (_.get(out, par + 'children') || []).length;
name2path[obj.name] = path + '.';
_.set(out, path, obj);
});
out = out.children;
将名称转换为name2path
表路径:它使用单个键_.set
初始化,并且迭代添加到它。在运行此代码后,查看此empty
的内容非常有用:
name2path
注意迭代如何递增索引以在{
"empty": "",
"foo": "children.0.",
"buzz": "children.1.",
"foo.bar": "children.0.children.0.",
"buzz.fizz": "children.1.children.0.",
"foo.hello": "children.0.children.1.",
"foo.hello.world": "children.0.children.1.children.0."
}
属性数组中存储多个条目。
最终结果children
:
out
嵌入式代码段只包含没有中间JSON的代码,可以分散您的注意力。
这比我之前的提交更好吗?我是这么认为的:这里的簿记要少得多,不透明,繁忙的代码和更高层次的构造。我认为缺乏递归会有所帮助。我认为最终[
{
"name": "foo",
"url": "/somewhere1",
"templateUrl": "foo.tpl.html",
"data": {
"title": "title A",
"subtitle": "description A"
},
"children": [
{
"name": "foo.bar",
"url": "/somewhere2",
"templateUrl": "anotherpage.tpl.html",
"data": {
"title": "title B",
"subtitle": "description B"
}
},
{
"name": "foo.hello",
"data": {},
"children": [
{
"name": "foo.hello.world",
"url": "/",
"templateUrl": "world.tpl.html",
"data": {
"title": "title D",
"subtitle": "description D"
}
}
]
}
]
},
{
"name": "buzz",
"data": {},
"children": [
{
"name": "buzz.fizz",
"url": "/another/place",
"templateUrl": "hello.tpl.html",
"data": {
"title": "title C",
"subtitle": "description C"
}
}
]
}
]
可能会替换为forEach
,但我没有尝试过,因为算法的其余部分是基于矢量和迭代的,我不想要与此分道扬。
很遗憾在ES6中留下了所有内容,我非常喜欢它:)
reduce
&#13;