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)
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.
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.
- bootstrap: ms-2 (margin start)
- fontawesome: fa-caret-right, fa-caret-down if you don't want to use it, you can use โธ(25B8)โพ(25BE) as the before contents
<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>
vanilla JavaScript