我被这个问题卡住了。
我有这个JSON文件
[
{
"id": 1,
"name": "Sales",
"superdepartment": null
},
{
"id": 2,
"name": "Engineering",
"superdepartment": null
},
{
"id": 3,
"name": "Product",
"superdepartment": null
},
{
"id": 4,
"name": "Design",
"superdepartment": 3
},
{
"id": 5,
"name": "Inbound Sales",
"superdepartment": 1
},
{
"id": 6,
"name": "Outbound Sales",
"superdepartment": 1
},
{
"id": 7,
"name": "Application Security",
"superdepartment": 2
},
{
"id": 8,
"name": "Front-End",
"superdepartment": 2
},
{
"id": 9,
"name": "Sales Development",
"superdepartment": 6
},
{
"id": 10,
"name": "Product Management",
"superdepartment": 3
}
]
所以,我需要根据所需的级别 递归地扩展 "超级部门 "的关系。比如说,我需要把 "超级部门 "的关系按照所需级别递归展开。
其实我有这段代码,可以满足第一层,但是我有几个问题,替换嵌套的对象来打开第二层关系。
departments.js --> 这里我得到数据(json),然后调用 "getRelations "方法。
module.exports.getAll = async function getAll(expand = null) {
let response = await data;
if (expand) {
response = modelUtils.getRelations(response, expand, response);
}
return response;
}
modelUtils.js --> 这里我写了我的核心函数来履行嵌套对象。
const _ = require('lodash');
//targetEntity is de JSON that I will use to get the nested entities from my actual ID.
// In this case is the same json, but can be another different.
module.exports.getRelations = function getRelations(entity, expand, targetEntity) {
let tmpEntity = _.cloneDeep(entity);
let path = expand.split('.');
for (let i=0; i < entity.length; i++) {
tmpEntity[i] = fillRelations(entity[i], path, targetEntity);
}
return tmpEntity;
}
function fillRelations(entity, path, targetEntity, level = 0) {
let current = _.cloneDeep(entity);
const currentPath = path[level];
if (!current[currentPath]) {
return current;
}
let value = targetEntity.filter(target => target.id === current[currentPath]);
if (value.length > 0) {
current[currentPath] = value[0];
}
level++;
return fillRelations(current, path, targetEntity, level);
}
所以实际上,用这段代码,并把?expand=superdepartment.superdepartment传给我的端点,我得到了这个JSON响应。
[
{
"id": 1,
"name": "Sales",
"superdepartment": null
},
{
"id": 2,
"name": "Engineering",
"superdepartment": null
},
{
"id": 3,
"name": "Product",
"superdepartment": null
},
{
"id": 4,
"name": "Design",
"superdepartment": {
"id": 3,
"name": "Product",
"superdepartment": null
}
},
{
"id": 5,
"name": "Inbound Sales",
"superdepartment": {
"id": 1,
"name": "Sales",
"superdepartment": null
}
},
{
"id": 6,
"name": "Outbound Sales",
"superdepartment": {
"id": 1,
"name": "Sales",
"superdepartment": null
}
},
{
"id": 7,
"name": "Application Security",
"superdepartment": {
"id": 2,
"name": "Engineering",
"superdepartment": null
}
},
{
"id": 8,
"name": "Front-End",
"superdepartment": {
"id": 2,
"name": "Engineering",
"superdepartment": null
}
},
{
"id": 9,
"name": "Sales Development",
"superdepartment": {
"id": 6,
"name": "Outbound Sales",
"superdepartment": 1
}
},
{
"id": 10,
"name": "Product Management",
"superdepartment": {
"id": 3,
"name": "Product",
"superdepartment": null
}
}
]
如你所见,ID=9元素需要为id=1打开第二层嵌套关系,所以它必须像这样。
{
"id": 9,
"name": "Sales Development",
"superdepartment": {
"id": 6,
"name": "Outbound Sales",
"superdepartment": {
"id": 1,
"name": "Sales",
"superdepartment": null
}
}
},
我不太确定这是否适合上面的代码库,但我认为它解决了你正在寻找的问题。
const expand = (field, lookups, xs) =>
xs.map (x => x[field] == null ? x : {...x, [field]: lookups.find(({id}) => id == x[field])})
const expandAll = ([field, ...fields], lookups, xs) =>
field == undefined
? xs
: fields .length > 0
? expandAll (fields, expand (field, lookups, xs), xs)
: // else
expand (field, lookups, xs)
const fillRelations = (expansionStr, xs) =>
expandAll (expansionStr .split ('.'), xs, xs)
const departments = [{ id: 1, name: "Sales", superdepartment: null }, { id: 2, name: "Engineering", superdepartment: null }, { id: 3, name: "Product", superdepartment: null }, { id: 4, name: "Design", superdepartment: 3 }, { id: 5, name: "Inbound Sales", superdepartment: 1 }, { id: 6, name: "Outbound Sales", superdepartment: 1 }, { id: 7, name: "Application Security", superdepartment: 2 }, { id: 8, name: "Front-End", superdepartment: 2 }, { id: 9, name: "Sales Development", superdepartment: 6 }, { id: 10, name: "Product Management", superdepartment: 3}]
console .log (
JSON.stringify (
fillRelations ('superdepartment.superdepartment', departments)
, null, 4)
)
.as-console-wrapper {min-height: 100% !important; top: 0}
我们定义了 expand
,它接收一个字段名、一个已经展开的项目数组和一个要查找的项目数组,并通过在展开的列表中查找来更新最后一个列表 (lookups
)匹配给定字段的那一个。 我们可以使用这样的方法。
expand('superdepartment', departments, departments)
然后我们在此基础上用 expandAll
,它接收一个字段名数组并递归调用自己和 expand
来填写缺失字段的详细信息。 我们可以使用这样的方式。
expandAll(['superdepartment', 'superdepartment'], departments, departments)
最后,我们给我们的公共API fillRelations
它通过将输入的字符串分割成一个数组,并将我们的初始对象作为查找列表和要展开的项目传入,从而启动了这个过程。 这就是你想要的签名。
fillRelations('superdepartment.superdepartment', departments)
我们添加了 JSON.stringify
调用来跳过SO控制台的引用字符串化。 但请注意,例如,第一个结果和那个 superdepartement
第六个结果的属性和 superdeparment.superdepartment
属性的第九个结果都引用同一个对象。 我看到你在代码中做了一些克隆,如果你不想要这些共享引用,你可以更新一下 expand
返回 克隆的对象。
这里有一个直接的(和递归的)解决方案来解决你的问题。
const data = [{
"id": 1,
"name": "Sales",
"superdepartment": null
}, {
"id": 2,
"name": "Engineering",
"superdepartment": null
}, {
"id": 3,
"name": "Product",
"superdepartment": null
}, {
"id": 4,
"name": "Design",
"superdepartment": 3
}, {
"id": 5,
"name": "Inbound Sales",
"superdepartment": 1
}, {
"id": 6,
"name": "Outbound Sales",
"superdepartment": 1
}, {
"id": 7,
"name": "Application Security",
"superdepartment": 2
}, {
"id": 8,
"name": "Front-End",
"superdepartment": 2
}, {
"id": 9,
"name": "Sales Development",
"superdepartment": 6
}, {
"id": 10,
"name": "Product Management",
"superdepartment": 3
}
];
function compute(data, expand) {
const path = expand.split('.');
return data.map(x => attachEntities(x, data, path));
}
function attachEntities(obj, data, [prop, ...props]) {
return prop ? {
...obj,
[prop]: obj[prop] && attachEntities(data.find(y => y.id === obj[prop]) || obj[prop], data, props)
}
: obj;
}
console.log('One level', compute(data, 'superdepartment'));
console.log('Two levels', compute(data, 'superdepartment.superdepartment'));
console.log('Three levels', compute(data, 'superdepartment.superdepartment.superdepartment'));