我有一个像这样的 JSON 树结构。
[
{
"title": "Blogs",
"id": "blogs",
"type": "array",
"children": [
{
"title": "Today",
"id": "today",
"type": "string"
},
{
"title": "Yesterday",
"id": "yesterday",
"type": "enum",
"options": [
"Y1",
"Y2"
]
}
]
},
{
"title": "Links",
"id": "links",
"type": "object",
"children": [
{
"title": "Oracle",
"id": "oracle",
"children": [
{
"title": "USA",
"id": "usa",
"type": "array",
"children": [
{
"title": "Midwest",
"id": "midwest",
"type": "enum",
"options": [
"Md1",
"Md2"
]
},
{
"title": "West",
"id": "west",
"type": "boolean"
}
]
},
{
"title": "Asia",
"id": "asia",
"type": "array",
"children": [
{
"title": "India",
"id": "india",
"type": "string"
}
]
}
]
}
]
}
]
我想要一个递归函数,它接受 2 个参数(第一个参数是实际的树数据,第二个参数是带点表示法的路径)并返回节点的类型(字符串/对象/数组/布尔)和枚举值(如果类型)是枚举。点表示法路径可能包含数组索引 0 或 1 等。 基本上我想要的是
var nodeType = getType(treeData, 'links.oracle.usa.0.midwest'); // Note: there is a 0 as usa is an array type
console.log(nodeType); // Should return [{"type":"enum"},{"options": ["md1", "md2"]}]
var nodeType = getType(treeData, 'blogs.0.today');
console.log(nodeType); // Should return [{"type":"string"}]
看起来像工作代码,它也处理错误的路径:
const sample = [
{
"title": "Blogs",
"id": "blogs",
"type": "array",
"children": [
{
"title": "Today",
"id": "today",
"type": "string"
},
{
"title": "Yesterday",
"id": "yesterday",
"type": "enum",
"options": [
"Y1",
"Y2"
]
}
]
},
{
"title": "Links",
"id": "links",
"type": "object",
"children": [
{
"title": "Oracle",
"id": "oracle",
"children": [
{
"title": "USA",
"id": "usa",
"type": "array",
"children": [
{
"title": "Midwest",
"id": "midwest",
"type": "enum",
"options": [
"Md1",
"Md2"
]
},
{
"title": "West",
"id": "west",
"type": "boolean"
}
]
},
{
"title": "Asia",
"id": "asia",
"type": "array",
"children": [
{
"title": "India",
"id": "india",
"type": "string"
}
]
}
]
}
]
}
]
const getType = (tree, path) => {
if (!path.length) return
const element = getElementFromTree(tree, path.split('.'))
if (!element || !element.type) return
const res = [{ type: element.type }]
if (element.options) {
res.push({ options: element.options })
}
return res
}
const getElementFromTree = (treePart, path) => {
const prop = path.shift()
if (!path.length) {
return treePart.id === prop ? treePart : undefined
}
let nextTreePart;
if (Array.isArray(treePart)) {
nextTreePart = treePart.find(v => v.id === prop)
} else if (isNaN(prop)) {
nextTreePart = treePart.children.find(v => v.id === prop)
} else {
nextTreePart = treePart.children[prop]
}
if (!nextTreePart) return
if (path.length) {
return getElementFromTree(nextTreePart, path)
}
return nextTreePart
}
// work as expected:
console.log(getType(sample, 'links.oracle.usa.0.midwest'))
console.log(getType(sample, 'links.oracle.usa.1.west'))
console.log(getType(sample, 'blogs.0.today'))
console.log(getType(sample, 'blogs.1.yesterday'))
console.log(getType(sample, 'links.oracle.asia.0.india'))
// tests with wrong paths, all return undefined
console.log(getType(sample, 'links.oracle.usa.5.west')) // because 5th element doesn't exists
console.log(getType(sample, 'blogs.3.today')) // because 3rd element doesn't exists
console.log(getType(sample, 'links.oracle')) // because links.oracle doesn't contain type field in it
console.log(getType(sample, '10.this.is.wrong.path')) // because path doesn't exist at all
我更愿意将其分成几个函数。树搜索代码以像
["links", "oracle", "usa", "midwest"]
这样的路径和具有 children
数组属性的数据对象开始,返回该路径处的节点,如果不存在则返回 undefined
。
然后我们编写一个简单的包装器,将您的
"links.oracle.usa.0.midwest"
字符串转换为该数组,并将输入数组包装到新对象的 children
属性中。此 getNode
函数还返回节点或 undefined
。这是一个独立有用的功能。
然后,因为您最终需要节点类型,所以我们添加简单的包装器
getType
来报告节点的类型,如果未找到,则添加 "unknown"
。我们可以轻松地将 "unknown"
替换为 undefined
或您以明显方式选择的任何内容。
const findInTree = (xs) => ([p = undefined, ...ps]) =>
xs == undefined
? undefined
: p == undefined
? xs
: findInTree (xs .children .find (({id}) => id == p)) (ps)
const getNode = (xs) => (path) =>
findInTree ({children: xs}) (path .split ('.') .filter (isNaN))
const getType = (xs) => (path) =>
(getNode (xs) (path) || {type: 'unknown'}) .type
const data = [{title: "Blogs", id: "blogs", type: "array", children: [{title: "Today", id: "today", type: "string"}, {title: "Yesterday", id: "yesterday", type: "enum", options: ["Y1", "Y2"]}]}, {title: "Links", id: "links", type: "object", children: [{title: "Oracle", id: "oracle", children: [{title: "USA", id: "usa", type: "array", children: [{title: "Midwest", id: "midwest", type: "enum", options: ["Md1", "Md2"]}, {title: "West", id: "west", type: "boolean"}]}, {title: "Asia", id: "asia", type: "array", children: [{title: "India", id: "india", type: "string"}]}]}]}];
console .log (getType (data) ("links.oracle.usa.0.midwest")) //~> "enum"
console .log (getType (data) ("links.oracle.usa")) //~> "array"
console .log (getType (data) ("blogs.0.today")) //~> "string"
console .log (getType (data) ("blogs.2.tomorrow")) //~> "unknown"
这些功能都相当简单。递归清晰;职责划分应该简单明了。
但我必须在这里做出一个假设。正如另一个答案中指出的,数组索引和后面的 id 是多余的。我们可以增加递归函数的复杂性来处理这种情况,但这会导致代码变得丑陋。相反,在处理节点之前,我们删除数组索引。这就是
.filter (isNaN)
中 getNode
的用途。如果这不是所需的行为,例如,如果索引和 id 不匹配,您希望失败或返回 undefined
,那么我们就必须做一些完全不同的事情。我并没有真正遵循您需要索引和 id 的理由,但在对另一个答案的评论中,您似乎暗示这是您真正需要的 id。如果两者兼而有之,那么这项技术将需要大量且丑陋的修改。