Javascript 是否基于字符串属性将对象数组转换为对象嵌套数组?
我遇到了一个问题,试图将对象的平面数组转换为基于name属性的对象的嵌套数组 将Javascript 是否基于字符串属性将对象数组转换为对象嵌套数组?,javascript,arrays,tree,javascript-objects,lodash,Javascript,Arrays,Tree,Javascript Objects,Lodash,我遇到了一个问题,试图将对象的平面数组转换为基于name属性的对象的嵌套数组 将输入数组转换为类似所需输出数组结构的最佳方法是什么 var input = [ { name: 'foo', url: '/somewhere1', templateUrl: 'foo.tpl.html', title: 'title A', subtitle: 'description A' }, {
输入
数组转换为类似所需输出
数组结构的最佳方法是什么
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之类的库来实现所需的输出
非常感谢您的帮助。此解决方案仅使用本机JS方法。它当然可以优化,但我保持原样是为了更容易遵循(或者我希望是这样)。当JS通过引用传递对象时,我也注意不修改原始输入
var输入=[{
名称:“foo”,
url:“/somewhere1”,
templateUrl:'foo.tpl.html',
标题:“标题A”,
副标题:"描述A"
}, {
名称:“foo.bar”,
url:“/somewhere2”,
templateUrl:'anotherpage.tpl.html',
标题:“标题B”,
副标题:“描述B”
}, {
名称:“嗡嗡作响,嘶嘶作响”,
url:“/other/place”,
templateUrl:'hello.tpl.html',
标题:“标题C”,
副标题:"描述C"
}, {
名字:“富,你好,世界”,
url:“/”,
templateUrl:'world.tpl.html',
标题:“标题D”,
副标题:“描述D”
}];
//迭代输入数组元素
var desiredOutput=input.reduce(函数createOutput(arr,obj){
var name=obj.name.split('.');
//将输入元素对象复制为不修改原始输入
var newObj=Object.keys(obj).filter(函数skipName(key){
返回键!=='name';
}).reduce(函数copyObject(tempObj,键){
if(key.match(/url$/i)){
tempObj[key]=obj[key];
}
否则{
临时对象数据[键]=对象[键];
}
返回tempObj;
},{name:names[names.length-1],数据:{});
//使用可能的递归构建新的输出数组
buildArray(arr、names、newObj);
返回arr;
}, []);
document.write(“”+JSON.stringify(desiredOutput,null,4)+“”);
//按名称属性搜索数组元素对象的帮助器函数
函数findIndexByName(arr,name){
对于(变量i=0,len=arr.length;i
使用Lodash(因为你到底为什么要在没有实用程序库的情况下操作复杂数据)。给你
它的工作原理是重塑初始输入,以便将名称拆分为数组并添加数据/子属性。然后,它通过buildTree
减少数据,该树使用一个变异函数(:())在reduce中的给定路径插入当前项
奇怪的if(!match)
部分确保在初始数据集中未使用URL等明确指定缺少的段时,将其添加进来
最后两行实际上完成了这项工作,可能是在一个小函数中,它可以与一些JSDoc一起完成。遗憾的是,我没有完全递归,我依靠数组变异将route对象插入树的深处
不过,应该足够简单。以下是我基于Lodash的尝试 首先,我发现
\.set
可以理解深度嵌套的对象表示法,因此我使用它来构建编码父子关系的树:
{
"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"
}
}
}
这将产生:
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;
事实上,这与期望的输出相差甚远,但孩子们的名字与其他属性(如title
)一起显示为属性
然后是一个费劲的过程,编写了一个递归函数,它采用了这个中间树,并以您希望的方式重新格式化它:
子属性数组中
foo.hello.world
中的hello
这样的中间节点没有任何数据,因此它必须插入data:{}
和name
属性数据
属性中,并清理所有仍然完全限定的名称
[
{
"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"
}
]
输出:
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 nameList = _.sortBy(_.pluck(input, 'name'));
var structure = {};
var mapNav = function(name, navItem) {
return {
name : name,
url : navItem.url,
templateUrl : navItem.templateUrl,
data : { title : navItem.title, subtitle : navItem.subtitle },
children : []
};
};
_.map(nameList, function(fullPath) {
var path = fullPath.split('.');
var parentItem = {};
_.forEach(path, function(subName, index) {
var navItem = _.find(input, { name : fullPath });
var item = mapNav(subName, navItem);
if (index == 0) {
structure[subName] = item;
} else {
parentItem.children.push(item);
}
parentItem = item;
});
});
var finalStructure = Object.keys(structure).map(function(key) {
return structure[key];
});
console.log(finalStructure);
这个解决方案不使用递归,它使用指向对象图中前一项的引用指针 注意这个解决方案在这里使用了lodash.JSFiddle示例
这是一个使用lodash的完全无递归的方法。当我想到
.set
和.get
有多好时,我想到了这一点,我意识到我可以用子对象的序列替换对象“路径”
首先,构建一个对象/哈希表,其键等于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);
(不要试图JSON.stringify
这个对象!因为它的值都是未定义的,所以它的计算结果是{}
。)
接下来,对每个元素应用两种转换:(1)将标题和副标题清理到子属性数据中,以及(2)这有点棘手,找到所有中间路径,如buzz
和foo.hello
,它们在input
中没有表示,但它们的子级是。展平此数组,并按name
字段中的
数量对它们进行排序
[
{
"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"
}
}
]
这段代码可能看起来很吓人,但看到它的输出应该会有帮助
var names = _.object(_.pluck(input, 'name'));
// { foo: undefined, foo.bar: undefined, buzz.fizz: undefined, foo.hello.world: undefined }
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);
[
{
"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"
}
}
]
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;
{
"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."
}
[
{
"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"
}
}
]
}
]