【问题标题】:Printing a JSON object in hierarchical/tree format以分层/树格​​式打印 JSON 对象
【发布时间】:2020-12-29 06:25:32
【问题描述】:

对象:

[
   {
      "Item":{
         "Name":"User 4"
      },
      "Children":[
         
      ]
   },
   {
      "Item":{
         "Name":"User 1"
      },
      "Children":[
         {
            "Item":{
               "Name":"User 6"
            }
         },
         {
            "Item":{
               "Name":"User 2"
            }
         }
      ]
   }
]

我正在使用以下代码遍历这个对象:

(function Traverse(o) {
    for (var i in o) {
        console.log('Value: ' + o[i].Item.Name);

        if (o[i].Children !== null && o[i].Children !== [] && typeof(o[i].Children) == "object") {
            Traverse(o[i].Children);
        }
    }
  })
(data);

输出:

value: User 4
value: User 1
value: User 6
value: User 2
undefined

我希望输出采用分层/树格​​式。我找到了几个库,但我不想要一个正确的图形表示,只是简单地使用文本来指示层次结构。

类似这样的:

附:我不是 javascript 程序员。

【问题讨论】:

  • 我正在从您的代码中获取输出 user 4, user 1, user 6, user 2。不就是你想要的吗?
  • @amar_1995 抱歉,我忘了写更新的输出。但我有兴趣以分层/树格​​式获取输出,如上图所示。
  • 您能否将所需的输出与示例输入对齐?目前他们是无关的。最重要的是,如果根数组中有多个元素,输出不会显示该怎么做。

标签: javascript jquery json tree


【解决方案1】:

你可以使用这个递归函数。例如,我使用了一个包含更多项目和级别的对象:

function toText(arr) {
    const recur = ({Item, Children}) => Item?.Name + 
        (Children?.length ? "\n" + Children.map(recur).map((text, i, {length}) =>
            i < length-1 ? "├──" + text.replace(/\n/g, "\n│  ")
                         : "└──" + text.replace(/\n/g, "\n   ")
        ).join("\n") : "")
    return arr.map(recur).join("\n");
}

// Example:
let arr = [{
    "Item": {
        "Name": "A"
    },
    "Children": [
    ]
}, {
    "Item": {
        "Name": "B"
    },
    "Children": [{
        "Item": {
           "Name": "BA"
        },
        "Children": [{
            "Item": {
                "Name": "BAA"
            },
            "Children": [{
                "Item": {
                    "Name": "BAAA"
                }
            }, {
                "Item": {
                    "Name": "BAAB"
                }
            }, {
                "Item": {
                    "Name": "BAAC"
                }
            }]
        }, {
            "Item": {
                "Name": "BAB"
            },
            "Children": [{
                "Item": {
                    "Name": "BABA"
                }
            }]
       }]
    }, {
        "Item":{
            "Name": "BB"
        }
    }]
}];
console.log(toText(arr));

【讨论】:

    【解决方案2】:

    编辑:我发现在 sn-p 中执行代码时格式有点损坏。如果将代码粘贴到devtools并运行,则格式正确。

    我找到了一个 npm 包 oo-ascii-tree 来执行此操作。这是您要求的示例:

    /**
     * A tree of nodes that can be ASCII visualized.
     */
    class AsciiTree {
        /**
         * Creates a node.
         * @param text The node's text content
         * @param children Children of this node (can also be added via "add")
         */
        constructor(text, ...children) {
            this.text = text;
            this._children = new Array();
            for (const child of children) {
                this.add(child);
            }
        }
        /**
         * Prints the tree to an output stream.
         */
        printTree(output = process.stdout) {
            let ancestorsPrefix = '';
            for (const parent of this.ancestors) {
                // -1 represents a "hidden" root, and so it's children
                // will all appear as roots (level 0).
                if (parent.level <= 0) {
                    continue;
                }
                if (parent.last) {
                    ancestorsPrefix += '  ';
                }
                else {
                    ancestorsPrefix += ' │';
                }
            }
            let myPrefix = '';
            let multilinePrefix = '';
            if (this.level > 0) {
                if (this.last) {
                    if (!this.empty) {
                        myPrefix += ' └─┬ ';
                        multilinePrefix += ' └─┬ ';
                    }
                    else {
                        myPrefix += ' └── ';
                        multilinePrefix = '     ';
                    }
                }
                else {
                    if (!this.empty) {
                        myPrefix += ' ├─┬ ';
                        multilinePrefix += ' │ │ ';
                    }
                    else {
                        myPrefix += ' ├── ';
                        multilinePrefix += ' │   ';
                    }
                }
            }
            if (this.text) {
                output.write(ancestorsPrefix);
                output.write(myPrefix);
                const lines = this.text.split('\n');
                output.write(lines[0]);
                output.write('\n');
                for (const line of lines.splice(1)) {
                    output.write(ancestorsPrefix);
                    output.write(multilinePrefix);
                    output.write(line);
                    output.write('\n');
                }
            }
            for (const child of this._children) {
                child.printTree(output);
            }
        }
        /**
         * Returns a string representation of the tree.
         */
        toString() {
            let out = '';
            this.printTree({
                write: (data) => (out += data),
            });
            return out;
        }
        /**
         * Adds children to the node.
         */
        add(...children) {
            for (const child of children) {
                child.parent = this;
                this._children.push(child);
            }
        }
        /**
         * Returns a copy of the children array.
         */
        get children() {
            return this._children.map((x) => x);
        }
        /**
         * @returns true if this is the root node
         */
        get root() {
            return !this.parent;
        }
        /**
         * @returns true if this is the last child
         */
        get last() {
            if (!this.parent) {
                return true;
            }
            return (this.parent.children.indexOf(this) === this.parent.children.length - 1);
        }
        /**
         * @returns the node level (0 is the root node)
         */
        get level() {
            if (!this.parent) {
                // if the root node does not have text, it will be considered level -1
                // so that all it's children will be roots.
                return this.text ? 0 : -1;
            }
            return this.parent.level + 1;
        }
        /**
         * @returns true if this node does not have any children
         */
        get empty() {
            return this.children.length === 0;
        }
        /**
         * @returns an array of parent nodes (from the root to this node, exclusive)
         */
        get ancestors() {
            if (!this.parent) {
                return [];
            }
            return [...this.parent.ancestors, this.parent];
        }
    }
    
    const arrEg = [
       {
          "Item":{
             "Name":"User 4"
          },
          "Children":[
             
          ]
       },
       {
          "Item":{
             "Name":"User 1"
          },
          "Children":[
             {
                "Item":{
                   "Name":"User 6"
                }
             },
             {
                "Item":{
                   "Name":"User 2"
                }
             }
          ]
       }
    ];
    
    function obj2tree(obj, tree) {
        const subTree = new AsciiTree(`${obj.Item.Name}`);
        if(obj.hasOwnProperty("Children")){
        obj.Children.forEach(o => {
            obj2tree(o, subTree);
        });
       }
      tree.add(subTree);
    }
    
    const treeStr = (objarr => {
        const tree = new AsciiTree('root');
        objarr.forEach(obj => {
        obj2tree(obj, tree);
       });
       return tree.toString();
    })(arrEg);
    
    console.log(treeStr);

    【讨论】:

      【解决方案3】:

      这是使用object-scan 的另一种方法:首先将输入转换为树结构,然后使用您选择的existing library 将其转换为树表示。

      好处是这个解决方案不使用递归(即使在引擎盖下)——因此对于深度嵌套的数据来说,堆栈溢出应该是不可能的。

      请注意,名称冲突将自动合并。因此,根据是否需要,我也确实喜欢 @trincot 发布的解决方案。

      // const objectScan = require('object-scan');
      // const objectTreeify = require('object-treeify');
      
      const data = [ { Item: { Name: 'A' }, Children: [] }, { Item: { Name: 'B' }, Children: [ { Item: { Name: 'BA' }, Children: [ { Item: { Name: 'BAA' }, Children: [ { Item: { Name: 'BAAA' } }, { Item: { Name: 'BAAB' } }, { Item: { Name: 'BAAC' } } ] }, { Item: { Name: 'BAB' }, Children: [ { Item: { Name: 'BABA' } } ] } ] }, { Item: { Name: 'BB' } } ] } ];
      
      const treeify = (input) => {
        const tree = objectScan(['**[*]'], {
          reverse: false,
          breakFn: ({ isMatch, value, context }) => {
            if (isMatch) {
              const cur = context[context.length - 1];
              const name = value.Item.Name;
              if (!(name in cur)) {
                cur[name] = {};
              }
              context.push(cur[name]);
            }
          },
          filterFn: ({ context }) => {
            context.pop();
          }
        })(input, [{}])[0];
        return objectTreeify(tree);
      };
      
      console.log(treeify(data));
      /* =>
      ├─ A
      └─ B
         ├─ BA
         │  ├─ BAA
         │  │  ├─ BAAA
         │  │  ├─ BAAB
         │  │  └─ BAAC
         │  └─ BAB
         │     └─ BABA
         └─ BB
       */
      .as-console-wrapper {max-height: 100% !important; top: 0}
      <script src="https://bundle.run/object-scan@13.8.0"></script>
      <script src="https://bundle.run/object-treeify@1.1.31"></script>

      免责声明:我是object-scanobject-treeify 的作者

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-01-13
        • 2013-10-25
        • 1970-01-01
        • 2016-10-08
        相关资源
        最近更新 更多