如何使用 JavaScript 获取 JSON Tree Schema 的 属性 值?
How to get property value of JSON Tree Schema using JavaScript?
我有一个 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"
}
]
}
]
}
]
}
]
我想要一个递归函数,它有两个参数(第一个参数是实际的树数据,第二个参数是带点符号的路径)和 returns 节点的类型(string/object/array/boolean)如果类型为枚举,则为枚举值。点符号路径可能包含数组索引,如 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"}]
使用 Lodash get
method 它允许:_.get(object, 'a[0].b.c');
。从不存在的路径中获取值是安全的——不会抛出错误。
看起来像工作代码,它也处理错误的路径:
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
希望对您有所帮助<3
我更愿意将其分解为几个函数。树搜索代码以 ["links", "oracle", "usa", "midwest"]
之类的路径和具有 children
数组 属性 的数据对象开始,return 在该路径处的节点,或 undefined
如果它不存在。
然后我们编写一个简单的包装器将您的 "links.oracle.usa.0.midwest"
字符串转换为该数组,并将您的输入数组包装到新对象的 children
属性 中。此 getNode
函数还 return 节点或 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 不匹配,您希望失败或 return undefined
,那么我们必须做一些完全不同的事情.我并没有真正理解您需要索引和 ID 的理由,但在对另一个答案的评论中,您似乎暗示这是您真正需要的 ID。如果两者兼而有之,那么这项技术将需要进行繁重且丑陋的修改。
我有一个 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"
}
]
}
]
}
]
}
]
我想要一个递归函数,它有两个参数(第一个参数是实际的树数据,第二个参数是带点符号的路径)和 returns 节点的类型(string/object/array/boolean)如果类型为枚举,则为枚举值。点符号路径可能包含数组索引,如 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"}]
使用 Lodash get
method 它允许:_.get(object, 'a[0].b.c');
。从不存在的路径中获取值是安全的——不会抛出错误。
看起来像工作代码,它也处理错误的路径:
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
希望对您有所帮助<3
我更愿意将其分解为几个函数。树搜索代码以 ["links", "oracle", "usa", "midwest"]
之类的路径和具有 children
数组 属性 的数据对象开始,return 在该路径处的节点,或 undefined
如果它不存在。
然后我们编写一个简单的包装器将您的 "links.oracle.usa.0.midwest"
字符串转换为该数组,并将您的输入数组包装到新对象的 children
属性 中。此 getNode
函数还 return 节点或 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 不匹配,您希望失败或 return undefined
,那么我们必须做一些完全不同的事情.我并没有真正理解您需要索引和 ID 的理由,但在对另一个答案的评论中,您似乎暗示这是您真正需要的 ID。如果两者兼而有之,那么这项技术将需要进行繁重且丑陋的修改。