Search code examples
javascriptarraysjsonrecursionhierarchical-data

How to get property value of JSON Tree Schema using JavaScript?


I have a JSON Tree structure like this.

[
  {
    "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"
              }
            ]
          }
        ]
      }
    ]
  }
]

I want a recursive function which takes 2 arguments(1st argument is The actual Tree Data and 2nd argument is a path with dot notation) and returns the type of the node (string/object/array/boolean) and enum values if the type is enum. The dot notation path may contain the array index as 0 or 1 or so on. Basically what i want is

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"}]

Solution

  • Seems like working code, which handles wrong paths as well:

    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