如何使用 JavaScript 获取 JSON Tree Schema 的属性值?

问题描述 投票:0回答:3

我有一个像这样的 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"}]
javascript arrays json recursion hierarchical-data
3个回答
2
投票

看起来像工作代码,它也处理错误的路径:

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


1
投票

我更愿意将其分成几个函数。树搜索代码以像

["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。如果两者兼而有之,那么这项技术将需要大量且丑陋的修改。


0
投票

使用 Lodash

get
方法 它允许:
_.get(object, 'a[0].b.c');
。从不存在的路径获取值是安全的 - 不会抛出错误。

© www.soinside.com 2019 - 2024. All rights reserved.