JSON Viewer

格式化显示JSON使数据看起来更加漂亮,支持折叠/展开格式化后的数据,支持JSON脑图让调用层级看着更清晰,支持复制JSON脑图节点路径

As of 2024-10-08. See the latest version.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @license      MIT
// @name         JSON Viewer
// @namespace    http://tampermonkey.net/
// @version      0.5.9
// @note         v0.5.9 jsonp格式小优化
// @note         v0.5.8 增加JSON手动输入
// @note         v0.5.7 一些小细节优化
// @note         v0.5.6 修复BUG
// @note         v0.5.5 解决@require jquery-simple-tree-table.min.js依赖加载失败问题
// @note         v0.5.4 单击复制修改为CTRL+单击复制JSONPath功能,JSON格式化风格增加table格式
// @note         v0.5.3 增加暗黑主题色
// @note         v0.5.2 单击JSON格式化的key可复制JSONPath
// @note         v0.5.1 修复JSONPath提示有误
// @note         v0.5.0 解决chrome 120+以上内核JSON格式化不执行和引入layer报错问题
// @note         v0.4.9 布局修改,增加保存JSON/脑图为文件,增加JSON过滤,鼠标移入key提示JSONPath
// @note         v0.4.8 代码优化
// @note         v0.4.7 增加对JSONP的判断,代码优化
// @note         v0.4.6 增加复制按钮,JSON脑图CSS样式细节优化,JSON脑图增加收起/展开子节点按钮
// @note         v0.4.5 在json-viewer-updated原基础上进行了一些修改,主要有CSS样式修改,新增折叠/展开全部功能,新增JSON脑图功能,脑图节点点击显示调用路径
// @description   格式化显示JSON使数据看起来更加漂亮,支持折叠/展开格式化后的数据,支持JSON脑图让调用层级看着更清晰,支持复制JSON脑图节点路径
// @author       Feny
// @match        *://*/*
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        unsafeWindow
// @grant        GM_setClipboard 
// @grant        GM_getResourceText
// @icon         data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAeAB4AAD/4QAiRXhpZgAATU0AKgAAAAgAAQESAAMAAAABAAEAAAAAAAD/2wBDAAIBAQIBAQICAgICAgICAwUDAwMDAwYEBAMFBwYHBwcGBwcICQsJCAgKCAcHCg0KCgsMDAwMBwkODw0MDgsMDAz/2wBDAQICAgMDAwYDAwYMCAcIDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAz/wAARCAAgACADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD9wvjF8bLX4ZrHZx+XNqlwnmKjH5YUyRvb6kEAd8H058d1b47XV5Ir3eoN++bagaXYrN6KOBn2FfPPx5/aEutX8a+KNWhIubhruZLSNj8u1WMcIOP4QoXOOwNS/wDBM79lDTfjh8YNe+IXj63TxRdeHfKjsE1KMTxtdSbmMmxvlAiVV2IBsUybgAyKR9ZTy6lhsM69Xovm2+iPmKmOqYjEKjT6v5WXU9e8beKrjxZpp/s/xFrfhjWIwWs9X0q42z2kmPlZo2zDcxjqYZ0eNv7obDDf/YL/AG8L745eJNc+G3xCttP0n4qeEWZZ2sVZNP8AENsu0rd2ysS0ZKPG7RMSQsisCcukWd+3x4RtfhpqGk+JNPjS0g1mV7a8iT5UM4XesgHYsofdjglQepJPxwPE1x4V/au8LePtNkeK40ma0lmdDgyorvHMhPo9uxjP+y1bUcHRxmGbS1adn1TXT0/4fcipiqmExCi3pfVdGn19f+GLni/w7ceGf2wPFXga+Vo5rW/vDbI3WWI/v4HA/wBqBg3tk+lfWX7AXia1+F/iDV9B1CRbWHXjFLayyHannpuUxk+rqy4zxlMdWAPQft7fsL33x91nQfiD4DutP0r4oeDXVrT7cWWx1y3UsTZ3LKCyAh5FEigkLLIpHzBk5vwj4JuvE2kLJfeHdY8N6lGAl5pepwBZrOT+JRIuYp0ByBNCzxPg4bIICqYyljMKot62Sa6prr6P/gBDC1MLiHJLS90+jT6ev/Dnmn/BVP8Aaw0nxv4/8P8Aw18KXK69qmk3MlxqEdiRMwuivlpbrtPLopkMnZNy5IIYLyPwa/Z8vvEmv+HtHu4/Ovr+5iS6KDcqAtukwe6om7nuEJxX0XoH7PK/bJG0vRoY5rr/AFslvbLGZf8AfcAf+PGvafgv8CbX4byNqFyI5tWmXYCoytsh6qvqx7t+A4yWiWYUsLhlRpbr72318kVHA1MTiHVqbP7kl0P/2Q==
// @require      https://code.jquery.com/jquery.min.js
// @require      https://unpkg.com/[email protected]/es6/jsmind.js
// @require      https://unpkg.com/[email protected]/dist/dom-to-image.min.js
// @require      https://unpkg.com/[email protected]/es6/jsmind.screenshot.js
// @resource     swalStyle https://unpkg.com/[email protected]/style/jsmind.css
// ==/UserScript==


/**
 * jquery-simple-tree-table.min.js
 * https://cdn.jsdelivr.net/npm/@kanety/[email protected]/dist/jquery-simple-tree-table.min.js
 */
!function(e){var t={};function n(o){if(t[o])return t[o].exports;var i=t[o]={i:o,l:!1,exports:{}};return e[o].call(i.exports,i,i.exports,n),i.l=!0,i.exports}n.m=e,n.c=t,n.d=function(e,t,o){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:o})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(n.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var i in e)n.d(o,i,function(t){return e[t]}.bind(null,i));return o},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/dist",n(n.s=5)}([function(e,t){e.exports=jQuery},function(e,t,n){var o=n(2),i=n(3);"string"==typeof(i=i.__esModule?i.default:i)&&(i=[[e.i,i,""]]);var a={insert:"head",singleton:!1};o(i,a);e.exports=i.locals||{}},function(e,t,n){"use strict";var o,i=function(){return void 0===o&&(o=Boolean(window&&document&&document.all&&!window.atob)),o},a=function(){var e={};return function(t){if(void 0===e[t]){var n=document.querySelector(t);if(window.HTMLIFrameElement&&n instanceof window.HTMLIFrameElement)try{n=n.contentDocument.head}catch(e){n=null}e[t]=n}return e[t]}}(),r=[];function s(e){for(var t=-1,n=0;n<r.length;n++)if(r[n].identifier===e){t=n;break}return t}function c(e,t){for(var n={},o=[],i=0;i<e.length;i++){var a=e[i],c=t.base?a[0]+t.base:a[0],l=n[c]||0,u="".concat(c," ").concat(l);n[c]=l+1;var d=s(u),f={css:a[1],media:a[2],sourceMap:a[3]};-1!==d?(r[d].references++,r[d].updater(f)):r.push({identifier:u,updater:y(f,t),references:1}),o.push(u)}return o}function l(e){var t=document.createElement("style"),o=e.attributes||{};if(void 0===o.nonce){var i=n.nc;i&&(o.nonce=i)}if(Object.keys(o).forEach((function(e){t.setAttribute(e,o[e])})),"function"==typeof e.insert)e.insert(t);else{var r=a(e.insert||"head");if(!r)throw new Error("Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid.");r.appendChild(t)}return t}var u,d=(u=[],function(e,t){return u[e]=t,u.filter(Boolean).join("\n")});function f(e,t,n,o){var i=n?"":o.media?"@media ".concat(o.media," {").concat(o.css,"}"):o.css;if(e.styleSheet)e.styleSheet.cssText=d(t,i);else{var a=document.createTextNode(i),r=e.childNodes;r[t]&&e.removeChild(r[t]),r.length?e.insertBefore(a,r[t]):e.appendChild(a)}}function h(e,t,n){var o=n.css,i=n.media,a=n.sourceMap;if(i?e.setAttribute("media",i):e.removeAttribute("media"),a&&btoa&&(o+="\n/*# sourceMappingURL=data:application/json;base64,".concat(btoa(unescape(encodeURIComponent(JSON.stringify(a))))," */")),e.styleSheet)e.styleSheet.cssText=o;else{for(;e.firstChild;)e.removeChild(e.firstChild);e.appendChild(document.createTextNode(o))}}var p=null,v=0;function y(e,t){var n,o,i;if(t.singleton){var a=v++;n=p||(p=l(t)),o=f.bind(null,n,a,!1),i=f.bind(null,n,a,!0)}else n=l(t),o=h.bind(null,n,t),i=function(){!function(e){if(null===e.parentNode)return!1;e.parentNode.removeChild(e)}(n)};return o(e),function(t){if(t){if(t.css===e.css&&t.media===e.media&&t.sourceMap===e.sourceMap)return;o(e=t)}else i()}}e.exports=function(e,t){(t=t||{}).singleton||"boolean"==typeof t.singleton||(t.singleton=i());var n=c(e=e||[],t);return function(e){if(e=e||[],"[object Array]"===Object.prototype.toString.call(e)){for(var o=0;o<n.length;o++){var i=s(n[o]);r[i].references--}for(var a=c(e,t),l=0;l<n.length;l++){var u=s(n[l]);0===r[u].references&&(r[u].updater(),r.splice(u,1))}n=a}}}},function(e,t,n){(t=n(4)(!1)).push([e.i,".simple-tree-table-icon{display:inline-block;width:1.5em;line-height:1.5em;margin:0.1em;background-color:#eee;text-align:center;cursor:pointer}.simple-tree-table-opened .simple-tree-table-icon:after{content:'-'}.simple-tree-table-closed .simple-tree-table-icon:after{content:'+'}\n",""]),e.exports=t},function(e,t,n){"use strict";e.exports=function(e){var t=[];return t.toString=function(){return this.map((function(t){var n=function(e,t){var n=e[1]||"",o=e[3];if(!o)return n;if(t&&"function"==typeof btoa){var i=(r=o,s=btoa(unescape(encodeURIComponent(JSON.stringify(r)))),c="sourceMappingURL=data:application/json;charset=utf-8;base64,".concat(s),"/*# ".concat(c," */")),a=o.sources.map((function(e){return"/*# sourceURL=".concat(o.sourceRoot||"").concat(e," */")}));return[n].concat(a).concat([i]).join("\n")}var r,s,c;return[n].join("\n")}(t,e);return t[2]?"@media ".concat(t[2]," {").concat(n,"}"):n})).join("")},t.i=function(e,n,o){"string"==typeof e&&(e=[[null,e,""]]);var i={};if(o)for(var a=0;a<this.length;a++){var r=this[a][0];null!=r&&(i[r]=!0)}for(var s=0;s<e.length;s++){var c=[].concat(e[s]);o&&i[c[0]]||(n&&(c[2]?c[2]="".concat(n," and ").concat(c[2]):c[2]=n),t.push(c))}},t}},function(e,t,n){"use strict";n.r(t);var o=n(0),i=n.n(o);function a(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function r(e,t){for(var n=0;n<t.length;n++){var o=t[n];o.enumerable=o.enumerable||!1,o.configurable=!0,"value"in o&&(o.writable=!0),Object.defineProperty(e,o.key,o)}}function s(e,t,n){return t&&r(e.prototype,t),n&&r(e,n),e}var c=function(){function e(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};a(this,e),this.opts={type:t.type||"session",key:t.key},this.inst=new l(this.opts)}return s(e,[{key:"get",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null;return this.inst.get(this.opts.key)||e}},{key:"set",value:function(e){this.inst.set(this.opts.key,e)}},{key:"remove",value:function(){this.inst.remove(this.opts.key)}}]),e}(),l=function(){function e(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};a(this,e),this.storage={local:window.localStorage,session:window.sessionStorage}[t.type]}return s(e,[{key:"get",value:function(e){try{var t=this.storage.getItem(e);return t?JSON.parse(t):null}catch(e){return console.log(e),null}}},{key:"set",value:function(e,t){try{this.storage.setItem(e,JSON.stringify(t))}catch(e){console.log(e)}}},{key:"remove",value:function(e){this.storage.removeItem(e)}}]),e}(),u=(n(1),"simple-tree-table");function d(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function f(e,t){for(var n=0;n<t.length;n++){var o=t[n];o.enumerable=o.enumerable||!1,o.configurable=!0,"value"in o&&(o.writable=!0),Object.defineProperty(e,o.key,o)}}var h={expander:null,collapser:null,opened:"all",margin:20,iconPosition:"> :first-child",iconTemplate:"<span />",store:null,storeKey:null},p=function(){function e(t){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};d(this,e),this.options=i.a.extend({},h,n),this.$table=i()(t),this.$expander=i()(this.options.expander),this.$collapser=i()(this.options.collapser),this.options.store&&this.options.storeKey&&(this.store=new c({type:this.options.store,key:this.options.storeKey})),this.init(),this.load()}var t,n,o;return t=e,o=[{key:"getDefaults",value:function(){return h}},{key:"setDefaults",value:function(e){return i.a.extend(h,e)}}],(n=[{key:"init",value:function(){this.$table.addClass(u),this.build(),this.unbind(),this.bind()}},{key:"destroy",value:function(){var e=function(e,t){var n=new RegExp("".concat(u,"(-\\S+)?"),"g");return(t.match(n)||[]).join(" ")};this.$table.removeClass(e),this.nodes().removeClass(e),this.$table.find(".".concat(u,"-icon")).remove(),this.unbind()}},{key:"build",value:function(){var e=this;this.nodes().not("[data-node-depth]").each((function(t,n){var o=i()(n),a=e.depth(o);o.data("node-depth",a),1==a&&o.addClass("".concat(u,"-root"))})),this.nodes().filter((function(t,n){return 0==i()(n).find(e.options.iconPosition).find(".".concat(u,"-handler")).length})).each((function(t,n){var o=i()(n),a=e.depth(o),r=e.options.margin*(a-1),s=i()(e.options.iconTemplate).addClass("".concat(u,"-handler ").concat(u,"-icon")).css("margin-left","".concat(r,"px"));o.find(e.options.iconPosition).prepend(s)})),this.nodes().not(".".concat(u,"-empty, .").concat(u,"-opened, .").concat(u,"-closed")).each((function(t,n){var o=i()(n);e.hasChildren(o)?e.opensDefault(o)?o.addClass("".concat(u,"-opened")):o.addClass("".concat(u,"-closed")):o.addClass("".concat(u,"-empty"))})),this.nodes().filter(".".concat(u,"-opened")).each((function(t,n){e.show(i()(n))})),this.nodes().filter(".".concat(u,"-closed")).each((function(t,n){e.hide(i()(n))}))}},{key:"opensDefault",value:function(e){var t=this.options.opened;return t&&("all"==t||-1!=t.indexOf(e.data("node-id")))}},{key:"bind",value:function(){var e=this;this.$expander.on("click.".concat(u),(function(t){e.expand()})),this.$collapser.on("click.".concat(u),(function(t){e.collapse()})),this.$table.on("click.".concat(u),"tr .".concat(u,"-handler"),(function(t){var n=i()(t.currentTarget).closest("tr");n.hasClass("".concat(u,"-opened"))?e.close(n):e.open(n)}))}},{key:"unbind",value:function(){this.$expander.off(".".concat(u)),this.$collapser.off(".".concat(u)),this.$table.off(".".concat(u," node:open node:close"))}},{key:"expand",value:function(){var e=this;this.nodes().each((function(t,n){e.show(i()(n))})),this.save()}},{key:"collapse",value:function(){var e=this;this.nodes().each((function(t,n){e.hide(i()(n))})),this.save()}},{key:"nodes",value:function(){return this.$table.find("tr[data-node-id]")}},{key:"depth",value:function(e){var t=e.data("node-depth");if(t)return t;var n=this.findByID(e.data("node-pid"));return 0!=n.length?this.depth(n)+1:1}},{key:"open",value:function(e){this.show(e),this.save(),e.trigger("node:open",[e])}},{key:"show",value:function(e){e.hasClass("".concat(u,"-empty"))||(e.removeClass("".concat(u,"-closed")).addClass("".concat(u,"-opened")),this.showDescs(e))}},{key:"showDescs",value:function(e){var t=this;this.findChildren(e).each((function(e,n){var o=i()(n);o.show(),o.hasClass("".concat(u,"-opened"))&&t.showDescs(o)}))}},{key:"close",value:function(e){this.hide(e),this.save(),e.trigger("node:close",[e])}},{key:"hide",value:function(e){e.hasClass("".concat(u,"-empty"))||(e.removeClass("".concat(u,"-opened")).addClass("".concat(u,"-closed")),this.hideDescs(e))}},{key:"hideDescs",value:function(e){var t=this;this.findChildren(e).each((function(e,n){var o=i()(n);o.hide(),t.hideDescs(o)}))}},{key:"hasChildren",value:function(e){return 0!=this.findChildren(e).length}},{key:"findChildren",value:function(e){var t=e.data("node-id");return this.$table.find('tr[data-node-pid="'.concat(t,'"]'))}},{key:"findDescendants",value:function(e){var t=this,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:[],o=this.findChildren(e);return n.push(o),o.each((function(e,o){t.findDescendants(i()(o),n)})),n}},{key:"findByID",value:function(e){return this.$table.find('tr[data-node-id="'.concat(e,'"]'))}},{key:"openByID",value:function(e){this.open(this.findByID(e))}},{key:"closeByID",value:function(e){this.close(this.findByID(e))}},{key:"load",value:function(){var e=this;if(this.store){var t=this.store.get();t&&(this.nodes().each((function(t,n){e.show(i()(n))})),this.nodes().filter((function(e,n){return-1!=t.indexOf(i()(n).data("node-id"))})).each((function(t,n){e.hide(i()(n))})))}}},{key:"save",value:function(){if(this.store){var e=this.nodes().filter(".".concat(u,"-closed")).map((function(e,t){return i()(t).data("node-id")})).get();this.store.set(e)}}}])&&f(t.prototype,n),o&&f(t,o),e}();i.a.fn.simpleTreeTable=function(e){return this.each((function(t,n){var o=i()(n);o.data(u)&&o.data(u).destroy(),o.data(u,new p(o,e))}))},i.a.SimpleTreeTable=p}]);

(function() {
    'use strict';

    const Utils = {
        // 检查字符串是否为URL
        isUrl: function (string) {
            var regexp = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/;
            return regexp.test(string);
        },

        // 检查是否是图片链接
        isImg: function (pathImg) {
            // var regexp = /^(http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?\/([\w#!:.?+=&%@!\-\/])*\.(gif|jpg|jpeg|png|GIF|JPG|PNG)([\w#!:.?+=&%@!\-\/])?/;
            let regexp = /\.(ico|bmp|gif|jpg|jpeg|png|svg|webp|GIF|JPG|PNG|WEBP|SVG)([\w#!:.?+=&%@!\-\/])?/i
            return regexp.test(pathImg);
        },
        // 检验内容是否是json格式的内容
        isJSON: function (str) {
            try {
                JSON.parse(str)
                return true
            } catch(e) {
                console.log("is not json", e)
                return false
            }
        },
        // 获取数据类型
        getType: function (value){
            return Object.prototype.toString.call(value).match(/\s(.+)]/)[1].toLowerCase();
        },
        // 获取数组中对象key最多的对象
        findMaxKeysObject: function(arr){
            let maxKeysCount = 0, maxKeysObject
            for (let obj of arr) {
                let keysCount = Object.keys(obj).length
                if(keysCount > maxKeysCount){
                    maxKeysCount = keysCount
                    maxKeysObject = obj
                }
            }
            return maxKeysObject
        }
    }

    // jquery.json-viewer 插件 开始
    // 解决和原网页jquery版本冲突
    var _jQuery = jQuery.noConflict(true);
    (function($){
        /**
         * 检查 arg 是否为至少包含 1 个元素的数组或至少包含 1 个键的字典
         */
        function isCollapsable(arg) {
            return arg instanceof Object && Object.keys(arg).length > 0;
        }

        /**
         * 将 JSON 对象转换为 HTML 表示形式
         * @return string
         */
        function json2html(json, parentPath = '') {
            let html = '', type = Utils.getType(json)
            switch(type){
                case 'array':
                case 'object':
                    let len = json.length || Object.keys(json).length;
                    if (len > 0) {
                    html += `<span class="json-brackets ${type == 'array' ? 'json-square-brackets' : 'json-curly-brackets'}">`;
                    html += type === 'array' ? '[</span><ol class="json-array">' : '{</span><ul class="json-object">';
                        for (var key in json) {
                            if (json.hasOwnProperty(key)) {
                                let comma  = --len > 0 ? ',' : '',
                                    jsonPath = parentPath + '.' + key,
                                    collapse = isCollapsable(json[key]) ? '<a href class="json-toggle"></a>' : '',
                                    res = json2html(json[key], jsonPath)
                                let toHtml = type === 'array' ? res : `<span class="json-key">"${key}"</span>: ${res}`
                                html += [`<li json-path="${jsonPath}">`, collapse, toHtml, comma, '</li>'].join('')
                            }
                        }

                        if(type === 'array'){
                            html += `</ol><span class="json-brackets ${type == 'array' ? 'json-square-brackets' : 'json-curly-brackets'}">]</span>`
                        }else{
                            html += `</ul><span class="json-brackets ${type == 'array' ? 'json-square-brackets' : 'json-curly-brackets'}">}</span>`
                        }
                    }else{
                        html += `<span class="json-brackets ${type == 'array' ? 'json-square-brackets' : 'json-curly-brackets'}">`
                        html += (type === 'array') ? '[]' : '{}'
                        html += '</span>'
                    }
                    break
                default:
                    /* Escape tags */
                    json = type === 'string' ? json.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;') : json
                    if (Utils.isUrl(json)){
                        html += `<a target="_blank" href="${json}" class="json-string">"${json}"</a>`;
                    }else{
                        json = type === 'string' ? `"${json}"` : json
                        html += `<span class="json-${type}">${json}</span>`;
                    }
                    break
            }
            return html;
        }

        $.fn.jsonViewer = function(json, jsonpFn) {
            return this.each(function() {
                /* Transform to HTML */
                var html = json2html(json);
                /** is JSONP */
                if(jsonpFn !== undefined && jsonpFn !== null){
                    html = `<div class="jsonp">${jsonpFn}(</div>${html}<div class="jsonp">)</div>`
                }
                /* Insert HTML in target DOM element */
                $(this).html(html);

                /* Bind click on toggle buttons */
                $(this).off('click');
                $(this).on('click', 'a.json-toggle', function() {
                    var target = $(this).toggleClass('collapsed').siblings('ul.json-object, ol.json-array');
                    target.toggle();
                    if (target.is(':visible')) {
                        target.siblings('.json-placeholder').remove();
                    }else {
                        var count = target.children('li:not([class*="hidden"])').length;
                        var placeholder = count + (count > 1 ? ' items' : ' item');
                        target.after('<a href class="json-placeholder">' + placeholder + '</a>');                    
                    }
                    return false;
                });

                /* Simulate click on toggle button when placeholder is clicked */
                $(this).on('click', 'a.json-placeholder', function() {
                    $(this).siblings('a.json-toggle').click();
                    $(this).siblings('a.json-placeholder').remove();
                    return false;
                });
            });
        };
    })(_jQuery);
    // jquery.json-viewer 插件 结束

    (function($){
        let docType = [
            "application/vnd.api+json",
            "application/javascript",
            "application/json",
            "text/javascript",
            "text/plain",
            "text/json",
        ], contentType = document.contentType
        if(!docType.includes(contentType)){
            return
        }

        var source = $('pre').first();
        if(source.length === 0){
            let text = document.body.innerText
            if(!Utils.isJSON(text)){
                return
            }

            let pre = document.createElement('pre')
            pre.innerText = text
            document.body.insertAdjacentHTML('afterbegin', pre);
            source = $(pre)
        }
        
        let rawText = source.text()
        if(!rawText){
            return
        }

        // 判断是否为jsonp格式
        window.globalJSONPFun = null
        let tokens = rawText.match(/^([^\s(]*)\s*\(([\s\S]*)\)\s*;?$/)
        if (tokens && tokens[1] && tokens[2]) {
            globalJSONPFun = tokens[1]
            rawText= tokens[2]
        }

        if(!Utils.isJSON(rawText)){
            return
        }

        // 随机rgb颜色
        let rgbaColor = `${Math.random()*256}, ${Math.random()*256}, ${Math.random()*256}`
        // 添加样式
        GM_addStyle(GM_getResourceText('swalStyle'))
        $("head").append(`<link rel="stylesheet" type="text/css" href="https://unpkg.com/[email protected]/dist/theme/default/layer.css"/>`)
        .append('<script src="https://unpkg.com/[email protected]/dist/layui.js" type="text/javascript" ></script>')

        GM_addStyle(`
            body, html{
                margin: 0;
                padding: 0;
                font-size: 14px;
            }
            td{
                font-size: 14px;
            }
            li::marker {
                content: '';
            }
            input:focus, select:focus {
				outline: 0;
			}
            .hidden{
                display: none !important;
            }
            .jsonp{
                color: #93983A;
            }
            .dark .jsonp{
                color: #F1D700;
            }
            .scroll-top{
                width: 48px;
                height: 48px;
                z-index: 999;
                position: fixed;
                right: 30px;
                bottom: 30px;
                display: none;
                background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAAXNSR0IArs4c6QAAA39JREFUaEPtmV2ITVEUx39rJjT5KPKC5DPKK6UGZZ7Eq0whNUiKFPfcKW9mnmjm7ssLaSLygEx5xItMESnKC4V8FV4IJUkyS+fMHZ07zjl3733PvT6a+3S6Z629/r+99sfa+wgpPw1YDtxLe9/k/1eI4X5STMkSokXWNlloYjgpMZSmIxPgbxBfS8M4QK0eavT78Qw0uodrtd+wDGjAMqBcEVAQw6NaYnzeNwRAi2xBOQlMrYj6jLJTygz6iMzyyR1Ai+xCGUgMqnTmDZErgBYoIJjMXhY2S4mLeWUiNwAt0INwyEqY0iNleq1saxjlAqAB64BrToKEHVLijJNPgnHdALqPaUziOsoKRzGfaKFD+nng6FdlXj+Ay9AZq1Q5L2W2/jEA3c8sWqOSe3aCiK/A3cr/K4G2FKEbxXDZF6KuDKROXKWXFvqlxJdQmBaZzDDdKZP8thhW/RmAgKfA4qrgSq+U6UkSlAostEuJOz4Q3hnQgPXAld+CCh1pB5DogKTcSBAalhpHmw1wGtgxJuhbMczJEqIBbxLmzFUxbGg2gCYE9AVADF6jwctJDzKd73xI7DG/IQQTmCFH+OiaBT+AAyykhWcpBZv7JA4bGmaRHOV5cwBqX7mc5Qe9coyX0TK6n/m0RnVSV4bA1KuTLCi/DBTYhHCpRm+Fe8Bo/b8JmJxp71lqOwNowB7guGuqLe33iuGEpW1k5gTgVDK7qIjbZmyESU1aA1TOuA99dTn5CQulxAsbH3uAeqpOGyXVNrvEcMrGzR4gIGnjsonhYzMkhg4bx3EAm17ysGlIBh4DSzzE+LgMiGG3jaPLEApv2Q7YNFq3jcOmZg9gt/u6aH8PzEx0aGWu9PHapjFrgKimCaIDTHiQqf4pNxHW2AT8ZaMEiZdgSreUKdm25QRQgRi7nIZzI7xZcPmeNkAL5xjmVpVQx1049HUGiCAKFBH6K8GjFUP3MIW2CGJpjd4bFEPnmM54grA761tYWpteAJXgy1A6w6p09Opcu1nED7ZlAcQP/BqwGZjORAblMO9sh03czhsgLZgW6EL4JoYL0b0RTArPBfFnH6G5ZyAVYKTkGBlWQVR2bxDDgvjzvwQQXqGsDQ/sGkTXKdHzOECsB3LtjdjKMjqE/qMMjJwn5olh+98+hM6gvAqXy0aN+4Yuo/HG/weAPmC1GNrzHDbxtn4Coc0pQNdM3UAAAAAASUVORK5CYII=)
            }
            /** 工具栏样式 START **/
            .flex-container{
                z-index: 10;
                position: fixed;
                width: 100vw;
                height: 100vh;
                display: flex;
                flex-direction: column;
            }
            .tabs, .toolbar{
                display: flex;
                line-height: 28px;
                background: #f3f3f3;
                border-bottom: 1px solid #e0e0e2;
            }
            .toolbar{
                line-height: 23px;
            }
            .searchbox{
                display: flex;
                flex-grow: 1;
            }
            .toolbar input{
                flex-grow: 1;
                border: none;
                outline: none;
                font-size: 12px;
                padding-left: 23px;
                background-size: 12px;
                background-repeat: no-repeat;
                background-position: 7px center;
                background-image: url(data:image/svg+xml;base64,PCEtLSBUaGlzIFNvdXJjZSBDb2RlIEZvcm0gaXMgc3ViamVjdCB0byB0aGUgdGVybXMgb2YgdGhlIE1vemlsbGEgUHVibGljCiAgIC0gTGljZW5zZSwgdi4gMi4wLiBJZiBhIGNvcHkgb2YgdGhlIE1QTCB3YXMgbm90IGRpc3RyaWJ1dGVkIHdpdGggdGhpcwogICAtIGZpbGUsIFlvdSBjYW4gb2J0YWluIG9uZSBhdCBodHRwOi8vbW96aWxsYS5vcmcvTVBMLzIuMC8uIC0tPgo8c3ZnIGZpbGw9InJnYmEoMTM1LCAxMzUsIDEzNywgMC45KSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgMTIgMTIiPgogIDxwYXRoIGZpbGw9ImNvbnRleHQtZmlsbCIgb3BhY2l0eT0iLjQiIGQ9Ik01IDkuMmwyIDEuNlY2LjFMOC41NSA0aC01LjFMNSA2LjF2My4xeiIvPgogIDxwYXRoIGZpbGw9ImNvbnRleHQtZmlsbCIgZD0iTTEuMTggMi42QTEgMSAwIDAgMSAyIDFIMTBhMSAxIDAgMCAxIC44IDEuNkw4IDYuNHY0LjgyYzAgLjYzLS43Mi45OC0xLjIyLjZsLTIuNS0xLjk5QS43NS43NSAwIDAgMSA0IDkuMjVWNi40MUwxLjE4IDIuNnpNMiAyTDUgNi4wOXYzLjA0bDIgMS41OVY2LjA5TDEwLjAxIDJIMnoiLz4KPC9zdmc+Cg==);
            }
            .clear {
                flex: 0 0 auto;
                align-self: center;
                margin: 0 4px;
                padding: 0;
                border: 0;
                width: 16px;
                height: 16px;
                background-color: transparent;
                background-image: url(data:image/svg+xml;base64,PCEtLSBUaGlzIFNvdXJjZSBDb2RlIEZvcm0gaXMgc3ViamVjdCB0byB0aGUgdGVybXMgb2YgdGhlIE1vemlsbGEgUHVibGljCiAgIC0gTGljZW5zZSwgdi4gMi4wLiBJZiBhIGNvcHkgb2YgdGhlIE1QTCB3YXMgbm90IGRpc3RyaWJ1dGVkIHdpdGggdGhpcwogICAtIGZpbGUsIFlvdSBjYW4gb2J0YWluIG9uZSBhdCBodHRwOi8vbW96aWxsYS5vcmcvTVBMLzIuMC8uIC0tPgo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiB2aWV3Qm94PSIwIDAgMTYgMTYiIGZpbGw9ImNvbnRleHQtZmlsbCIgZmlsbC1vcGFjaXR5PSJjb250ZXh0LWZpbGwtb3BhY2l0eSI+CiAgPHBhdGggZD0iTTYuNTg2IDhsLTIuMjkzIDIuMjkzYTEgMSAwIDAgMCAxLjQxNCAxLjQxNEw4IDkuNDE0bDIuMjkzIDIuMjkzYTEgMSAwIDAgMCAxLjQxNC0xLjQxNEw5LjQxNCA4bDIuMjkzLTIuMjkzYTEgMSAwIDEgMC0xLjQxNC0xLjQxNEw4IDYuNTg2IDUuNzA3IDQuMjkzYTEgMSAwIDAgMC0xLjQxNCAxLjQxNEw2LjU4NiA4ek04IDBhOCA4IDAgMSAxIDAgMTZBOCA4IDAgMCAxIDggMHoiLz4KPC9zdmc+Cg==);
            }
            .tabs-item{
                border-top: 2px solid #f3f3f3;
            }
            .tabs-item:hover{
                background: #e9e9e9;
                border-top: 2px solid #c3c3c6;
            }
            .tabs-item, .toolbar-item{
                cursor: pointer;
                padding: 0 10px;
                font-size: 12px;
                
            }
            .tabs-item.active{
                color: #0060df;
                border-top: 2px solid #0060df !important;
                background: #e9e9e9;
            }

            .tabs-item:hover, .toolbar-item:hover{
                background: #e9e9e9;
            }
            /** 工具栏样式 END **/
            /** JSON 格式化样式 START **/
            ul.json-object,
            ul.json-array {
                list-style-type: none;
                margin: 0 0 0 2px;
                border-left: 1px dotted #5D6D7E;
                padding-left: 24px;
            }
            .json-brackets {
                font-weight: 700;
            }
            .json-key {
                /* color: #A31515;*/
                color: #910F93;
                cursor: pointer;
            }
            .json-string, .json-string a{
                /* color: #0b7500;*/
                color: #4B8A4C;
            }
            .json-number{
                /* color: #164FF0;*/
                color: #1a01cc;
                font-weight: 600;
            }
            .json-boolean{
                color: #905;
                font-weight: 600;
            }
            .json-null {
                /* color: #F1592A;*/
                color: #0031BC;
                font-weight: 600;
            }
            a.json-toggle {
                position: rElative;
                color: inherit;
                opacity: 0.2;
                text-decoration: none;
            }
            a.json-toggle:hover {
                opacity: 0.35;
            }
            a.json-toggle:active {
                opacity: 0.5;
            }
            a.json-toggle:focus {
                outline: none;
            }
            a.json-toggle:before {
                top: 2.5px;
                left: -15px;
                position: absolute;
                content: "";
                display: block;
                width: 0;
                height: 0;
                border-style: solid;
                border-width: 5px 0 5px 8px;
                border-color: transparent transparent transparent currentColor;
                transform: rotate(90deg);
            }
            a.json-toggle.collapsed:before {
                transform: rotate(0deg);
            }
            a.json-placeholder {
                color: #aaa;
                font-size: 12px;
                padding: 0 1em;
                text-decoration: none;
            }
            a.json-placeholder:hover {
                text-decoration: underline;
            }
            
            .json-curly-brackets {
                color: #6D9331;
            }
            .json-square-brackets{
                color: #8E9331;
            }
            /** 暗黑主题 START **/
            body.dark{
                background: #333333;
            }
            .dark li, .dark pre{
                color: #CCC;
            }
            .dark .json-toggle {
                opacity: 0.35;
            }
            .dark .json-toggle:hover {
                opacity: 0.5;
            }
            .dark .json-curly-brackets {
                color: #CE70D6;
            }
            .dark .json-square-brackets{
                color: #F1D700;
            }
            .dark .json-key {
                color: #7CDCFE;
            }
            .dark .json-string, .dark .json-string a{
                color: #CE9178;
            }
            .dark .json-number{
                color: #B5CEA8;
            }
            .dark .json-boolean{
                color: #569CD6;
            }
            .dark .json-null {
                color: #2D7AD6;
            }
            .dark jmnode {
                color: #7CDCFE !important;
            }
            /** 暗黑主题 END **/
            /** JSON 格式化样式 END **/
            /** 脑图样式 START **/
            #jmContainer{
                width: 100vw; 
                height: calc(100vh - 57px);
                /* background:#F7F7F7 */
            }
            jmnode{
                display: flex;
                align-items: center;
                padding: 0 7px 0 22px;
            }
            jmnode{
                color: #475872 !important;
                box-shadow: none !important;
                background-color: transparent !important;
            }
            jmnode:hover{
                text-shadow: 0px 0px 1px currentColor;
            }
            jmnode.root {
                padding: 0;
                color: transparent !important;
            }
            jmnode:not(.root)::before, jmnode.root::before{
                content: " ";
                top: 50%;
                position: absolute;
                border-radius: 50%;
                transform: translateY(-50%);
            }
            jmnode:not(.root)::before{
                left: 0;
                width: 15px;
                height: 15px;
                background: rgba(${rgbaColor}, 0.5);
            }
            jmnode.root::before{
                left: 50%;
                width: 18px;
                height: 18px;
                transform: translate(-18px, -50%);
                background: rgba(${rgbaColor}, 0.7);
            }
            jmexpander{
                margin-top: 1px;
                line-height: 9px;
                background: #dfdfdf;
            }
            .mind-array{
                opacity: 0.6;
                font-size: 12px;
                padding-left: 5px;
            }
            /** 脑图样式 END **/
            /** 容器样式 START **/
            .tabs-container{
                overflow: auto;
                line-height: 1.4;
                font-family: monospace;
            }
            .tabs-container > div{
                display: none;
            }
            .tabs-container > div.active{
                display: block;
            }
            .tabs-container #formatContainer{
                padding: 10px;
            }
            .tabs-container #rawTextContainer{
                padding: 0 10px;
            }
            .tabs-container #rawTextContainer pre{
                display: block !important;
                overflow-wrap: break-word;
                white-space: pre-wrap;
            }
            /** 容器样式 END **/
            
            .layui-layer-tips{
                width: auto !important;
            }
            .tabs .selectbox{
                position: absolute;
                right: 200px;
                display: flex;
                font-size: 13px;
            }
            /** 表格 START **/
            table {
                width: -webkit-fill-available;
				margin-left: 20px;
                border-collapse: collapse;
			}
            table tr:hover{
                background: #f0f9fe;
            }
            .dark table tr:hover{
                background: #353B48
            }
            table tr.selected td, table tr.selected td a{
                color: #fff !important;
                background-color: #3875d7;
            }
            table tbody tr td:first-child{
                width: 120px;
            }
            
			.simple-tree-table-icon{
                color: #000;
                opacity: 0.2;
                width: 0 !important;
				margin: 0 !important;
                line-height: 0 !important;
			}
            .simple-tree-table-icon:hover {
                opacity: 0.35;
            }
            .simple-tree-table-icon:active {
                opacity: 0.5;
            }
            .simple-tree-table-icon:after{
                content: "" !important;
            }
            .simple-tree-table-icon:before {
                top: 0.5px;
                left: -15px;
                position: relative;
                content: "";
                width: 0;
                height: 0;
                display: none;
                border-style: solid;
                border-width: 5px 0 5px 8px;
                border-color: transparent transparent transparent currentColor;
                transform: rotate(90deg);
            }
            .simple-tree-table-opened .simple-tree-table-icon:before {
                display: block;
            }
            .simple-tree-table-closed .simple-tree-table-icon:before {
                display: block;
                transform: rotate(0deg);
            }
            .dark .simple-tree-table-icon{
                color: #FFF;
                opacity: 0.5;
            }
            .tree-len{
                color: #ccc;
            }
            textarea:focus {
				outline: 0;
			}
            .inputJson{
                cursor: pointer;
                color: #0060df;
                margin-left: 20px;
            }
            /** 表格 END **/
        `)

        source.hide()
        // 将内容用eval函数处理下
        window.globalJSON = eval(`(${rawText})`);

        $("body").addClass('dark').append(`
        <div class="scroll-top"></div>
        <div class="flex-container">
            <div class="panel">
                <div class="tabs">
                    <div class="tabs-item btn active" id="format">JSON格式化</div>
                    <div class="tabs-item btn" id="viewJsonMind">JSON脑图</div>
                    <div class="tabs-item btn" id="viewRawText">原始数据</div>
                    <div class="selectbox">
                        <div class="formatStyle">
                            <label>风格:</label>
                            <select>
                                <option value="default">默认</option>
                                <option value="treaTable">表格</option>
                            </select>
                        </div>
                        <div class="theme" style="margin: 0 10px">
                            <label>主题: </label>
                            <select>
                                <option value="default">默认</option>
                                <option value="dark">暗黑</option>
                            </select>
                        </div>
                        <span class="inputJson">JSON 输入</span>
                    </div>
                </div>
                <div class="toolbar">
                    <div class="toolbar-item btn" id="saveJson">保存</div>
                    <div class="toolbar-item btn" id="copyJson">复制</div>
                    <div class="toolbar-item btn" id="collapseAll">全部折叠</div>
                    <div class="toolbar-item btn" id="expandAll">全部展开</div>
                    <div class="toolbar-item btn" id="beautify" style="display: none">美化输出</div>
                    <div class="searchbox">
                        <input type="text" placeholder="过滤 JSON "/>
                        <button class="clear" hidden></button>
                    </div>
                </div>
            </div>
            <div class="tabs-container">
                <div class="active" id="formatContainer"></div>
                <div id="jmContainer"></div>
                <div id="rawTextContainer"><pre></pre></div>
            </div>
        </div>`) 

        let btnEvent = {
            firstFormat: true,
            isBeautify: false,
            $rawText: $('#rawTextContainer'),
            /**
             * 保存为文件
             */
            download: {
                download: function(content, filename) {
                    const link = document.createElement("a")
                    link.href = content
                    link.download = filename
                    link.click()
                },
                saveJSON: function (text) {
                    // 创建一个 Blob 对象,包含要下载的文本内容
                    const blob = new Blob([text], { type: "text/plain;charset=utf-8" });
                    const url = URL.createObjectURL(blob)
                    let filename = new Date().getTime() + '.json';
                    this.download(url, filename)
                    URL.revokeObjectURL(url);
                },
                savePNG: () => jm.shoot(),
            },
            saveJson:function(){
                if($('#jmContainer').is(':visible')){
                    this.download.savePNG()
                }else{
                    this.download.saveJSON(this.$rawText.text())
                }
            },
            // 复制JSON文本内容
            copyJson: function(){
                GM_setClipboard(this.$rawText.text())
                layer.msg('复制成功', {time: 1500})
            },
            // 全部折叠
            collapseAll: function(){
                if($('#formatContainer').is(':visible')){
                    try{
                        $('a.json-toggle').not('.collapsed').click()
                    }catch(e){}
                }else{
                    jm.collapse_all()
                }
            },
            // 全部展开
            expandAll: function(){
                if($('#formatContainer').is(':visible')){
                    try{
                        $('a.json-placeholder').click().remove()
                    }catch(e){}
                }else{
                    jm.expand_all()
                    jm.scroll_node_to_center(jm.get_root())
                }
            },
            format: function(){},
            // 显示JSON脑图
            viewJsonMind: function(){
                jsonMind.init(globalJSON)
            },
            // 查看原始JSON内容
            viewRawText: function(){
                if(this.firstFormat){
                    this.$rawText.html(source.clone())
                    this.firstFormat = false
                }
            },
            // 美化
            beautify: function(){
                this.isBeautify = !this.isBeautify
                if(this.isBeautify){
                    let str = JSON.stringify(globalJSON, null, 2)
                    if(globalJSONPFun !== undefined && globalJSONPFun !== null){
                        str = `${globalJSONPFun}(${str})`
                    }
                    this.$rawText.find('pre').text(str)
                }else{
                    this.$rawText.html(source.clone())
                }
            },
            init: function(){
                this.viewRawText()
                // 按钮点击事件
                $('.btn').click(e => {
                    const target = e.target, id = target.id
                    if(target.classList.contains('tabs-item')){
                        let index = $(target).index()
                        $(target).addClass('active').siblings().removeClass("active")
                        $('.tabs-container > div').removeClass("active").eq(index).addClass('active')

                        let beautifyEl = $('#beautify'), 
                            searchEl= $('.searchbox'),
                            copyJsonEl= $('#copyJson'),
                            aEl = $('#collapseAll, #expandAll')
                        id === 'format' ? searchEl.show(): searchEl.hide()
                        id === 'viewJsonMind' ? copyJsonEl.hide(): copyJsonEl.show()
                        id === 'viewRawText' ? (beautifyEl.show() && aEl.hide()) : (beautifyEl.hide() && aEl.show())
                    }
                    this[id](target)
                })

                return this
            }
        },
        jsonMind = {
            // JSON数据转换为jsMind所需要的数据结构
            convert: function(json){
                let children = []
                if(typeof json === 'object'){
                    for(let key in json){
                        let val = json[key], 
                            isArray = Array.isArray(val)
                        
                        if(isArray && val.length > 0){
                             val = Utils.findMaxKeysObject(val)
                        }

                        children.push({
                            isArray,
                            chain: key,
                            id: key + '_' + Math.random(),
                            topic: `${key}<span class="mind-array">${ Utils.getType(json[key]) }</span>`,
                            children: this.convert(val)
                        })
                    }
                }
                return children;
            },
            // 脑图节点调用链
            mindChain: function (node){
                let chain = node.data.chain
                if(!node.parent){
                    return chain
                }

                let parent = node.parent, parentChain = this.mindChain(parent)
                chain = parent.data.isArray ? `${parentChain}[0].${chain}` : `${parentChain}.${chain}`
                return chain
            },
            //  显示脑图
            show: function(json){
                let isArr = Array.isArray(json);
                if(isArr){
                    if(typeof json[0] !== 'object'){
                        layer.msg('数据结构无法生成脑图', {time: 1000})
                        return this
                    }
                    json = json[0]
                }

                jm.show({
                    "meta":{
                        "name":"JSON脑图",
                        "author":"[email protected]",
                        "version":"1.0"
                    },
                    "format":"node_tree",
                    /* 数据内容 */
                    "data": {
                        "id": "root",
                        "topic": 'Response',
                        "direction": "left",
                        "children": this.convert(json),
                        "chain": isArr ? 'Response[0]' : 'Response'
                    }
                })
                return this
            },
            // 脑图节点事件
            event:function(){
                $("jmnode").on('dblclick mouseover mouseout', function(event){
                    let that = $(this), node = jm.get_node(that.attr('nodeid'))
                    if(!node.parent){
                        return
                    }

                    switch(event.type){
                        case 'dblclick':
                            GM_setClipboard(jsonMind.mindChain(node))
                            layer.msg('节点路径复制成功', {time: 1500})
                            break;
                        case 'mouseover':
                            let s = `<b>节点路径(双击复制)</b><br/>${jsonMind.mindChain(node)}`
                            layer.tips(s, that, {
                                time: 0,
                                tips: [2, '#2e59a7']
                            });
                            break;
                        default:
                            layer.closeAll()
                            break;
                    }
                })
                return this
            },
            collapseOrExpand: function(){
                $("jmnode").on('click', function(){
                    let node = jm.get_node($(this).attr('nodeid'))
                    jm.toggle_node(node)
                })
                return this
            },
            init: function(json){
                if(!window.jm){
                    window.jm = new jsMind({
                        mode :'side', 
                        editable: false,
                        container:'jmContainer',
                        view: {
                            hmargin: 50, // 思维导图距容器外框的最小水平距离
                            vmargin: 50,  // 思维导图距容器外框的最小垂直距离
                            engine: 'svg', // 思维导图各节点之间线条的绘制引擎
                            draggable: true, // 当容器不能完全容纳思维导图时,是否允许拖动画布代替鼠标滚动
                            support_html : false, 
                            line_color: '#C4C9D0',
                        },
                        layout: {
                            vspace: 7, // 节点之间的垂直间距
                            hspace: 150, // 节点之间的水平空间
                        },
                    });
                }

                this.show(json).event().collapseOrExpand()
            }
        },
        otherOperate = {
            // 过滤 JSON
            filterJSON: function(filter) {
                let style = GM_getValue('formatStyle') || 'default'
                if(!filter){
                    style == 'default' ? $('#formatContainer li').removeClass('hidden') : $('.json-key').parent().removeClass('hidden')
                    return
                }

                let chainSet= new Set()
                /**
                 * 模糊匹配 JSON key
                 * 假如`filter`值为`id`, querySelectorAll得到DOM节点
                 * 得到:['.feedList.0.images.0.user_id', '.feedList.0.images.0', '.feedList.0.images', '.feedList.0', '.feedList']
                 */
                document.querySelectorAll('#formatContainer *[json-path]').forEach(el => {
                    let chain = $(el).attr('json-path')
                    if(!chain){
                        return
                    }
                    let newChain = chain.substr(chain.lastIndexOf('.'))
                    if(!newChain.toLowerCase().includes(filter.toLowerCase())){
                        return
                    }
                    chainSet.add(chain)
                    while(chain = chain.substr(0, chain.lastIndexOf('.'))){
                        chainSet.add(chain)
                    }
                })

                /**
                 * 模糊匹配 JSON value
                 */
                document.querySelectorAll("#formatContainer *[class*='json-']:not([class*='json-key']):not([class*='json-brackets'])")
                .forEach(el =>{
                    let target = $(el), 
                        chain = style == 'default' ? target.parent().attr('json-path') : target.siblings('.json-key').attr('json-path')
                    if(!chain){
                        return
                    }
                    let text = target.text()
                    if(!text.toLowerCase().includes(filter.toLowerCase())){
                        return
                    }
                    chainSet.add(chain)
                    while(chain = chain.substr(0, chain.lastIndexOf('.'))){
                        chainSet.add(chain)
                    }
                })
                console.log(chainSet)
                style == 'default' ? $('#formatContainer li').addClass('hidden') : $('.json-key').parent().addClass('hidden')
                chainSet.forEach(chain => {
                    style == 'default' ? $(`#formatContainer *[json-path="${chain}"]`).removeClass('hidden')
                        : $(`#formatContainer *[json-path="${chain}"]`).parent().removeClass('hidden')
                })
            },
            // JSON 过滤
            input: function(){
                let that = this
                $('input').on('input', function(){
                    let val = $(this).val()
                    val === '' ? $('.clear').attr('hidden', true) : $('.clear').attr('hidden', false)
                    that.filterJSON(val)
                })
                return that
            },
            // 清空输入框内容
            clear: function(){
                let that = this
                $('.clear').click(function(){
                    that.filterJSON()
                    $('input').val('')
                    $(this).attr('hidden', true)
                })
                return this
            },
            // 返回顶部
            scrollTop: function(){
                $('.scroll-top').click(function(){
                    $('.tabs-container').animate({
                        scrollTop: '0'
                    }, 1000);
                })
                $('.tabs-container').scroll(function(e) {
                    let scrollTop = $(this).scrollTop()
                    scrollTop > 500 ? $('.scroll-top').fadeIn() : $('.scroll-top').fadeOut()
                });
                return this
            },
            // 所有a标签,看是否是图片,是图片生成预览图  
            urlHover:function(){
                $("#formatContainer").on({
                    mouseenter: function(){
                        var that = $(this), 
                        href = that.attr('href')
                        if(Utils.isImg(href)){
                            layer.tips(`<img src="${href}" />`, that, {
                                time: 0,
                                anim: 5,
                                maxWidth: 500,
                                tips: [3, '#d9d9d9']
                            });
                        }
                    },
                    mouseleave: () => layer.closeAll()
                }, 'a[href]')
                return this
            },
            // 提示key的JSONPath
            tipsJsonPath: function(){
                var that = this
                $("#formatContainer").on({
                    mouseenter: function(){
                        let jsonPath = that.getJsonPath(this)
                        let tips = `<b>ctrl + 点击复制</b><br/>${jsonPath}`
                        layer.tips(tips, this, {
                            time: 0,
                            anim: 5,
                            maxWidth: 500,
                            tips: [1, '#2e59a7']
                        })
                    },
                    mouseleave: () => layer.closeAll()
                }, '.json-key')
                return this
            },
            // 单击key复制JSONPath
            copyJsonPath: function(){
                var that = this
                $("#formatContainer").on('click', '.json-key', function(event){
                    if(event.ctrlKey){
                        let jsonPath = that.getJsonPath(this)
                        GM_setClipboard(jsonPath)
                        layer.msg('复制成功', {time: 1500})
                    }
                })
                return this
            },
            getJsonPath: function(element){
                let style = GM_getValue('formatStyle') || 'default'
                let jsonPath = style == 'default' ? $(element).parent().attr('json-path') : $(element).attr('json-path')
                return jsonPath.split('.').reduce((prev, next) => /^\d+$/.test(next) ? prev + `[${next}]` : prev + '.' + next)
            },
            inputJson: function(){
                $('.inputJson').off('click').click(function(){
                    layer.prompt({
                        title: "JSON 输入",
                        formType: 2,
                        shadeClose: true,
                        maxlength: 1000000,
                    },function (text) {
                        if(!text){
                            layer.msg("内容不能为空", {time: 1500})
                            return
                        }

                        let rawText = text

                        // 判断是否为jsonp格式
                        let match = text.match(/^([^\s(]*)\s*\(([\s\S]*)\)\s*;?$/)
                        if (match && match[1] && match[2]) {
                            globalJSONPFun = match[1]
                            text= match[2]
                        }

                        try {
                            globalJSON = JSON.parse(JSON.stringify(eval(`(${text})`)));
                            source.text(rawText)
                            btnEvent.isBeautify = false
                            btnEvent.$rawText.html(source.clone())
                            formatStyle.setStyle(globalJSON, globalJSONPFun)
                            jsonMind.init(globalJSON)
                            layer.closeAll()
                        } catch (e) {
                            console.log(e)
                            layer.msg("JSON不合法", {time: 1500})
                        }
                    })
                })
                return this
            },
            init:function(){
                this.input().clear().scrollTop().urlHover().tipsJsonPath().copyJsonPath()
                .inputJson()
            }
        },
        theme = {
            // 切换主题
            changeTheme: function(){
                let that = this
                $('.theme select').change(function(e){
                    let val = $(e.target).val()
                    GM_setValue('theme', val)
                    that.setTheme()
                })
                return this
            },
            // 设置主题
            setTheme: function(){
                let theme = GM_getValue('theme') || 'default'
                $('body').removeClass().addClass(theme)
                $('.theme select').val(theme)
                return this
            },
            init:function(){
                this.setTheme().changeTheme()
            }
        },
        formatStyle = {
            // 切换风格
            changeStyle: function(json){
                let that = this
                $('.formatStyle select').change(function(e){
                    let val = $(e.target).val()
                    GM_setValue('formatStyle', val)
                    that.setStyle(json, globalJSONPFun)
                })
                return this
            },
            // 设置风格
            setStyle: function(json, jsonpFn){
                let style = GM_getValue('formatStyle') || 'default'
                $('.formatStyle select').val(style)

                $('input').val('')
                $('#formatContainer').html('')
                if(style === 'default'){
                    $('#formatContainer').jsonViewer(json, jsonpFn)
                }else{
                    let appendHtml = `<table id="treeTable">${treeTableHtml(json)}</table>`
                    if(globalJSONPFun !== undefined && globalJSONPFun !== null){
                        appendHtml = `<div class="jsonp">${globalJSONPFun}(</div>${appendHtml}<div class="jsonp">)</div>`
                    }
                    $('#formatContainer').append(appendHtml)
                    $('#treeTable').simpleTreeTable({
                        expander: '#expandAll',
                        collapser: '#collapseAll',
                    })

                    // Highlight selected row
                    $('#treeTable').on("mousedown", "tr", function() {
                        $(".selected").not(this).removeClass("selected");
                        $(this).toggleClass("selected")
                    })
                }
                return this
            },
            init:function(json, jsonpFn){
                this.setStyle(json, jsonpFn).changeStyle(json)
                theme.init()
                btnEvent.init()
                setTimeout(() => otherOperate.init(), 1000)
            }
        }

        formatStyle.init(globalJSON, globalJSONPFun)

        /**
         * 表格
         */
        function treeTableHtml(json, level = 0, pId = '', pChain = ''){
            let tr = ''
            for(let key in json){
                let val = json[key], 
                    type = Utils.getType(val), 
                    tId = key + '_' + Math.random(),
                    chain = pChain + "." + key
                if(['array', 'object'].includes(type)){
                    let brackets = '',
                        len = Object.keys(val).length,  
                        res = treeTableHtml(val, level + 1, tId, chain)

                    if(!res){
                        if(type ==='array'){
                            brackets = `<span class="json-brackets ${type == 'array' ? 'json-square-brackets' : 'json-curly-brackets'}">[]</span>`
                        }else{
                            brackets = `<span class="json-brackets ${type == 'array' ? 'json-square-brackets' : 'json-curly-brackets'}">{}</span>`
                        }
                    }

                    tr += `
                        <tr data-node-id="${tId}" data-node-pid="${pId}" type="${type}">
                            <td class="json-key" json-path="${chain}" style="padding-left: ${ level * 19 }px">
                                ${key}:
                                <span class="tree-len">${type ==='array' ? '[' + len + ']' : '{' + len + '}'}</span>
                            </td>
                            <td>${brackets}</td>
                        </tr>`
                    tr += res
                }else{
                    val = (type === 'string') ? val.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;') : val
                    tr += `<tr data-node-id="${tId}" data-node-pid="${pId}" type="${type}">
                        <td class="json-key" json-path="${chain}" style="padding-left: ${ level * 19 }px">${key}:</td>`
                    if (Utils.isUrl(val)){
                        tr += `<td class="json-${type}"><a target="_blank" href="${val}">"${val}"</a></td>`
                    }else{
                        val = (type === 'string') ? `"${val}"`: val
                        tr += `<td class="json-${type}">${val}</td>`
                    }
                    tr += '</tr>'
                }
            }
            return tr;
        }

    })(_jQuery)
})();