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

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


a good-looking, compact, collapsible tree view

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

Check out the demo


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>


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/bootstrap@5.0.2/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>