我有一个嵌套对象,我的目标是获取路径,直到键值对在数组中匹配。
我目前的实现是通过字符串实现的,并将字符串与dot (".")连接起来。
但是,我想将所有的中间结果添加到数组中并将其压入。
带示例的代码
const data = [
{
parentId: "1111",
name: "Audi",
children : [
{
parentId: "2222",
name: "Benz",
children : [
{
parentId: "3333",
name: "VW",
children : [
]
}
]
}
]
}
]
const pathTo = (array, target) => {
var result;
array.some(({ parentId, name, children = [] }) => {
if (parentId === target) {
return result = JSON.stringify({"parentId" : parentId, "name" : name});
}
var temp = pathTo(children, target)
if (temp) {
return result = JSON.stringify({"parentId" : parentId, "name" : name}) + "." + temp;
}
});
return result;
};
console.log(pathTo(data, "3333"))
当前结果
{"parentId":"1111","name":"Audi"}.{"parentId":"2222","name":"Benz"}.{"parentId":"3333","name":"VW"}
=比;与字符串连接的路径。但是我想:
预期结果
[ "{"parentId":"1111","name":"Audi"}", "{"parentId":"2222","name":"Benz"}"{"parentId":"3333","name":"VW"}"]
=比;
所有元素按顺序排列的数组。您可以返回一个数组或undefined
。
const
data = [{ parentId: "1111", name: "Audi", children : [{ parentId: "2222", name: "Benz", children : [{ parentId: "3333", name: "VW", children : [] }] }] }],
pathTo = (array, target) => {
let result;
array.some(({ parentId, name, children = [] }) => {
if (parentId === target) {
result = [{ parentId: parentId, name: name }];
return true;
}
const temp = pathTo(children, target)
if (temp) {
result = [{ parentId: parentId, name: name }, ...temp];
return true;
}
});
return result;
};
console.log(pathTo(data, "3333"))
.as-console-wrapper { max-height: 100% !important; top: 0; }
我更喜欢将这样的问题分解成几个部分,并在此过程中至少使其中一些更通用。
function * traversePaths (xs, path = []) {
for (let x of xs) {
const newPath = [...path, x]
yield newPath
yield * traversePaths (x .children || [], newPath)
}
}
const deepFindPath = (pred) => (xs) => {
for (let path of traversePaths (xs)) {
if (pred (path [path.length - 1])) {return path}
}
}
const pathByParentId = (target) =>
deepFindPath (({parentId}) => parentId == target)
const data = [{parentId: "1111", name: "Audi", children : [{parentId: "2222", name: "Benz", children : [{parentId: "3333", name: "VW", children : []}]}]}]
// stringified to avoid SO's `/**id:4**/` - `/**ref:4**/,` notation
console .log (JSON .stringify (
pathByParentId ('3333') (data)
, null, 4))
.as-console-wrapper {max-height: 100% !important; top: 0}
这里的traversePaths
是一个生成器函数,它接受任何元素具有(可选的,递归的)children
属性的数组,并遍历它(预顺序),为数组中的每个路径生成一个对象/子对象节点的数组。
deepFindPath
返回与所提供的谓词匹配的第一个节点的路径。
这两个函数是泛型的。然后,为了解决您的问题,我们编写简单的pathByParentId
,它简单地接受目标id并将一个函数传递给deepFindPath
,该函数测试节点的parentId
属性是否与目标id匹配。它将返回第一个匹配节点的路径,如果没有匹配,则返回undefined
。
您请求的输出与上述匹配,只是您没有提到children
节点。我更喜欢这个返回,只是对现有节点的简单引用。但是如果你真的不想要孩子,那么你可以这样做:
const pathByParentId = (target) => (
data,
res = deepFindPath (({parentId}) => parentId == target) (data)
) => res && res .map (({children, ...rest}) => rest)
虽然我们可以在遍历函数中这样做,但这会使该函数不那么泛型,可能也不那么有用。但是,如果您想这样做,您可以保留pathByParentId
的原始版本,并将traversePaths
替换为:
function * traversePaths (xs, path = []) {
for (let {children, ...rest} of xs) {
const newPath = [...path, rest]
yield newPath
yield * traversePaths (children || [], newPath)
}
}
这比以前的版本更不灵活,但它仍然通常列出任何元素具有(可选的,递归的)children
属性的数组的路径。这些列表中的元素是新对象,类似于原始对象,但缺少children
属性。
如果您可以使用依赖项,我将这样做。下面是一个使用对象扫描的解决方案。
.as-console-wrapper {max-height: 100% !important; top: 0}
<script type="module">
import objectScan from 'https://cdn.jsdelivr.net/npm/object-scan@18.1.2/lib/index.min.js';
const data = [{ parentId: '1111', name: 'Audi', children: [{ parentId: '2222', name: 'Benz', children: [{ parentId: '3333', name: 'VW', children: [] }] }] }];
const find = (obj, v) => objectScan(['**'], {
abort: true,
filterFn: ({ value }) => value === v,
rtn: ({ parents }) => parents
.filter((p) => !Array.isArray(p))
.reverse()
.map(({ parentId, name }) => ({ parentId, name }))
})(obj);
console.log(find(data, '3333'));
/* => [
{ parentId: '1111', name: 'Audi' },
{ parentId: '2222', name: 'Benz' },
{ parentId: '3333', name: 'VW' }
] */
</script>
免责声明:我是object-scan的作者