Display JSON data on a page as a expandable/collapsible list

12,344

Solution 1

a good-looking, compact, collapsible tree view

pgrabovets' json-view is amazingly clean and well designed.

Check out the demo

Solution 2

If you can consider using JS libraries , consider using JSON Formatter or Render JSON. Both these libraries offer configuration options like themes, maximum depth and sorting. To display simple JSON string in a collapsible form using Render JSON, you can use

<script>
    document.getElementById("test").appendChild(
        renderjson({ hello: [1,2,3,4], there: { a:1, b:2, c:["hello", null] } })
    );
</script>

Solution 3

Some of the links to the questions are no longer accessible. I assume you are looking for how to make a collapsible JSON view.


TL;DR

you can jump to Full code.

the code is very short (200 lines↓, including JSDoc, comment, test code.)

Inspire you on how to solve the problem.

This question in some skill is very much like how to make the table of contents. (TOC)

  1. First of all, JSON data is like an object. All we need to do is to add some more attributes (key, depth, children, ...) for each item.

  2. Once these actions are done, all left is to render, and here is the pseudo-code for rendering.

    render(node) {
      const divFlag = document.createRange().createContextualFragment(`<div style="margin-left:${node.depth * 18}px"></div>`)
      const divElem = divFlag.querySelector("div")
      const spanFlag = document.createRange().createContextualFragment(
        `<span class="ms-2">${node.key} : ${node.value}</span>`
      )
      node.children.forEach(subNode => {
        const subElem = render(subNode)
        spanFlag.append(subElem)
      })
      divElem.append(spanFlag)
      return divElem
    }
    

Full code

both CSS is not necessary.

<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet"
      integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossOrigin="anonymous">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css"
      integrity="sha512-1ycn6IcaQQ40/MKBW2W4Rhis/DbILU74C1vSrLJxCq57o941Ym01SwNsOMqvEBFlcgUa6xLiPY/NS5R+E6ztJQ=="
      crossOrigin="anonymous" referrerpolicy="no-referrer"/>

<script type="module">
  // 👇 main script {Node, Tree, JsonView}
  class Node {
    /**
     * @description Add more attributes to the item.
     * @param {*} item
     * @param {*} key
     * @param {Node} parent
     * */
    constructor(item, key, parent) {
      this.key = key

      /** @param {string} */
      this.type = Array.isArray(item) ? "array" : typeof item

      /** @param {Number} */
      this.depth = parent ? parent.depth + 1 : 0
      this.value = item
      this.parent = parent

      /** @param {[Node]} */
      this.children = []
    }
  }

  class Tree {
    /**
     * @description Given the root node, it will complete the children of it.
     * @param {Node} rootNode
     */
    constructor(rootNode) {
      this.root = rootNode

      const obj = this.root.value
      if (!(obj instanceof Object)) { // Array is an Object too.
        return
      }
      Object.keys(obj).forEach(keyOrIdx => {
        const value = obj[keyOrIdx]
        const subNode = new Node(value, keyOrIdx, rootNode)
        const subTree = new Tree(subNode)
        rootNode.children.push(subTree.root)
      })
    }

    /**
     * @param {string | Object} jsonData
     * @return {Tree}
     */
    static CreateTree(jsonData) {
      jsonData = typeof jsonData === "string" ? JSON.parse(jsonData) : jsonData
      const rootNode = new Node(jsonData, "root", null)
      return new Tree(rootNode)
    }
  }

  class JsonView {
    static DefaultColorMap = {
      text: {
        string: "green",
        number: "#f9ae58",
        boolean: "#ca4ff8",
        array: "black",
        object: "black",
      },
      bg: {
        object: "none"
        // ... You can add more by yourself. They are like the text as above.
      }
    }

    static NewConfig() {
      return JSON.parse(JSON.stringify(JsonView.DefaultColorMap))
    }

    static SEPARATOR = " : "

    /** @type {Tree} */
    #tree

    /**
     * @param {Tree} tree
     * */
    constructor(tree) {
      this.#tree = tree
    }

    /**
     * @param {Node} node
     * @param {Object} colorMap
     */
    #render(node, colorMap = JsonView.DefaultColorMap) {
      /**
       * @param {Node} node
       * */
      const getValue = (node) => {
        const typeName = node.type
        switch (typeName) {
          case "object":
            return `object {${Object.keys(node.value).length}}`
          case "array":
            return `array [${Object.keys(node.value).length}]`
          default:
            return node.value
        }
      }

      const arrowIcon = ["object", "array"].includes(node.type) ? `<i class="fas fa-caret-down"></i>` : ""
      const divFlag = document.createRange().createContextualFragment(`<div style="margin-left:${node.depth * 18}px">${arrowIcon}</div>`)
      const divElem = divFlag.querySelector("div")

      const textColor = colorMap.text[node.type] !== undefined ? `color:${colorMap.text[node.type]}` : ""
      const bgColor = colorMap.bg[node.type] !== undefined ? `background-color:${colorMap.bg[node.type]}` : ""
      const valueStyle = (textColor + bgColor).length > 0 ? `style=${[textColor, bgColor].join(";")}` : ""

      const keyName = node.depth !== 0 ? node.key + JsonView.SEPARATOR : "" // depth = 0 its key is "root" which is created by the system, so ignore it.
      const spanFlag = document.createRange().createContextualFragment(
        `<span class="ms-2">${keyName}<span ${valueStyle}>${getValue(node)}</span></span>`
      )

      const isCollapsible = ["object", "array"].includes(node.type)

      node.children.forEach(subNode => {
        const subElem = this.#render(subNode, colorMap)

        if (isCollapsible) {
          divFlag.querySelector(`i`).addEventListener("click", (e) => {
            e.stopPropagation()
            subElem.dataset.toggle = subElem.dataset.toggle === undefined ? "none" :
              subElem.dataset.toggle === "none" ? "" : "none"

            e.target.className = subElem.dataset.toggle === "none" ? "fas fa-caret-right" : "fas fa-caret-down" // Change the icon to → or ↓

            subElem.querySelectorAll(`*`).forEach(e => e.style.display = subElem.dataset.toggle)
          })
        }

        spanFlag.append(subElem)
      })
      divElem.append(spanFlag)
      return divElem
    }

    /**
     * @param {Element} targetElem
     * @param {?Object} colorMap
     */
    render(targetElem, colorMap = JsonView.DefaultColorMap) {
      targetElem.append(this.#render(this.#tree.root, colorMap))
    }
  }

  // 👇 Below is Test
  function main(outputElem) {
    const testObj = {
      db: {
        port: 1234,
        name: "My db",
        tables: [
          {id: 1, name: "table 1"},
          {id: 2, name: "table 2"},
        ],
      },
      options: {
        debug: false,
        ui: true,
      },
      person: [
        "Foo", 
        "Bar"
      ]
    }
    const tree = Tree.CreateTree(testObj)
    const jsonView = new JsonView(tree)
    jsonView.render(outputElem)
    /* If you want to set the color by yourself, you can try as below
    const config = JsonView.NewConfig()
    config.bg.object = "red"
    jsonView.render(outputElem, config)
     */
  }

  (() => {
    window.onload = () => {
      main(document.body)
    }
  })()
</script>

vanilla JavaScript

Share:
12,344
Admin
Author by

Admin

Updated on July 16, 2022

Comments

  • Admin
    Admin almost 2 years

    I need help with with displaying JSON data on a page like expandable/collapsible list.

    Here is a valid JSON I`ve made converting from XML with Python:

    JSON Data

    And to display it I`m usig this:

    <!DOCTYPE HTML>
    <head>
        <title>JSON Tree View</title>
        <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js" type="text/javascript"></script>
        <script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.2/jquery-ui.min.js" type="text/javascript"></script>
    
    </head>
    <script>
    function json_tree(object){
            var json="<ul>";
            for(prop in object){
                var value = object[prop];
                switch (typeof(value)){
                    case "object":
                        var token = Math.random().toString(36).substr(2,16);
                        json += "<li><a class='label' href='#"+token+"' data-toggle='collapse'>"+prop+"="+value+"</a><div id='"+token+"' class='collapse'>"+json_tree(value)+"</div></li>";
                    break;
                    default:
                    json += "<li>"+prop+"="+value+"</li>";
                }
            }
            return json+"</ul>";
    }
    </script>
    <body style="margin: 40px;">
    <h3>Paste JSON Into The Textarea Below and Click 'Build Tree'</h3>
    
    <textarea id="json" style="width: 100%;min-height:300px;">
    
    </textarea>
    <button onclick="$('#output').html(json_tree(JSON.parse($('#json').val())));">Build Tree</button>
    <div id="output">
    
    </div>
    </body>
    </html>
    

    This is what I get:

    Image

    I need help "filltering" (or merge with upper node) those "0" and "1", and also - how to show only values of the attributes without the names (or if do you have some better idea how can I dusplay this list)?

  • Yash Nasery
    Yash Nasery over 2 years
    the render json one works beautifully! thanks for this answer!!. for anyone looking for the script tag to add the script directly from github : <script src="cdn.rawgit.com/caldwell/renderjson/master/…>
  • woto
    woto over 2 years
    Cool, thanks for sharing stuff. Little addition. When a value is null it throwing exception. So it may be added after switch (typeName) - if(node.value) ...