JSON Viewer

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

当前为 2024-10-11 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @license MIT
  3. // @name JSON Viewer
  4. // @namespace http://tampermonkey.net/
  5. // @version 0.6.2
  6. // @note v0.6.2 脑图增加JSON Crack
  7. // @note v0.6.1 增加多一种浅色主题
  8. // @note v0.6.0 增加简单HTTP 请求功能,可请求GET/POST/PUT/DELETE的API接口,而不单单只能GET请求使用
  9. // @note v0.5.9 jsonp格式小优化
  10. // @note v0.5.8 增加JSON手动输入
  11. // @note v0.5.7 一些小细节优化
  12. // @note v0.5.6 修复BUG
  13. // @note v0.5.5 解决@require jquery-simple-tree-table.min.js依赖加载失败问题
  14. // @note v0.5.4 单击复制修改为CTRL+单击复制JSONPath功能,JSON格式化风格增加table格式
  15. // @note v0.5.3 增加暗黑主题色
  16. // @note v0.5.2 单击JSON格式化的key可复制JSONPath
  17. // @note v0.5.1 修复JSONPath提示有误
  18. // @note v0.5.0 解决chrome 120+以上内核JSON格式化不执行和引入layer报错问题
  19. // @note v0.4.9 布局修改,增加保存JSON/脑图为文件,增加JSON过滤,鼠标移入key提示JSONPath
  20. // @note v0.4.8 代码优化
  21. // @note v0.4.7 增加对JSONP的判断,代码优化
  22. // @note v0.4.6 增加复制按钮,JSON脑图CSS样式细节优化,JSON脑图增加收起/展开子节点按钮
  23. // @note v0.4.5 在json-viewer-updated原基础上进行了一些修改,主要有CSS样式修改,新增折叠/展开全部功能,新增JSON脑图功能,脑图节点点击显示调用路径
  24. // @description 格式化显示JSON使数据看起来更加漂亮,支持折叠/展开格式化后的数据,支持JSON脑图让调用层级看着更清晰,支持复制JSON脑图节点路径
  25. // @author Feny
  26. // @match *://*/*
  27. // @grant GM_addStyle
  28. // @grant GM_getValue
  29. // @grant GM_setValue
  30. // @grant unsafeWindow
  31. // @grant GM_openInTab
  32. // @grant GM_setClipboard
  33. // @grant GM_getResourceText
  34. // @grant GM_registerMenuCommand
  35. // @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==
  36. // @require https://code.jquery.com/jquery.min.js
  37. // @require https://unpkg.com/jsmind@0.8.5/es6/jsmind.js
  38. // @require https://unpkg.com/dom-to-image@2.6.0/dist/dom-to-image.min.js
  39. // @require https://unpkg.com/jsmind@0.8.5/es6/jsmind.screenshot.js
  40. // @resource swalStyle https://unpkg.com/jsmind@0.8.5/style/jsmind.css
  41. // ==/UserScript==
  42.  
  43.  
  44. /**
  45. * jquery-simple-tree-table.min.js
  46. * https://cdn.jsdelivr.net/npm/@kanety/jquery-simple-tree-table@0.5.1/dist/jquery-simple-tree-table.min.js
  47. */
  48. !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}]);
  49.  
  50.  
  51. (function () {
  52. 'use strict';
  53.  
  54. const Utils = {
  55. // 检查字符串是否为URL
  56. isUrl: function (string) {
  57. var regexp = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/;
  58. return regexp.test(string);
  59. },
  60.  
  61. // 检查是否是图片链接
  62. isImg: function (pathImg) {
  63. // var regexp = /^(http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?\/([\w#!:.?+=&%@!\-\/])*\.(gif|jpg|jpeg|png|GIF|JPG|PNG)([\w#!:.?+=&%@!\-\/])?/;
  64. let regexp = /\.(ico|bmp|gif|jpg|jpeg|png|svg|webp|GIF|JPG|PNG|WEBP|SVG)([\w#!:.?+=&%@!\-\/])?/i
  65. return regexp.test(pathImg);
  66. },
  67. // 检验内容是否是json格式的内容
  68. isJSON: function (str) {
  69. try {
  70. JSON.parse(str)
  71. return true
  72. } catch (e) {
  73. console.log("is not json", e)
  74. return false
  75. }
  76. },
  77. // 获取数据类型
  78. getType: function (value) {
  79. return Object.prototype.toString.call(value).match(/\s(.+)]/)[1].toLowerCase();
  80. },
  81. // 获取数组中对象key最多的对象
  82. findMaxKeysObject: function (arr) {
  83. let maxKeysCount = 0, maxKeysObject
  84. for (let obj of arr) {
  85. let keysCount = Object.keys(obj).length
  86. if (keysCount > maxKeysCount) {
  87. maxKeysCount = keysCount
  88. maxKeysObject = obj
  89. }
  90. }
  91. return maxKeysObject
  92. },
  93. // 随机rgb颜色
  94. rgbaColor: (opacity) => `rgba(${Math.random() * 256}, ${Math.random() * 256}, ${Math.random() * 256}, ${opacity})`
  95. }
  96.  
  97. // jquery.json-viewer 插件 开始
  98. // 解决和原网页jquery版本冲突
  99. var _jQuery = jQuery.noConflict(true);
  100. (function ($) {
  101. /**
  102. * 检查 arg 是否为至少包含 1 个元素的数组或至少包含 1 个键的字典
  103. */
  104. function isCollapsable(arg) {
  105. return arg instanceof Object && Object.keys(arg).length > 0;
  106. }
  107.  
  108. /**
  109. * 将 JSON 对象转换为 HTML 表示形式
  110. * @return string
  111. */
  112. function json2html(json, parentPath = '') {
  113. let html = '', type = Utils.getType(json)
  114. switch (type) {
  115. case 'array':
  116. case 'object':
  117. let len = json.length || Object.keys(json).length;
  118. if (len > 0) {
  119. html += `<span class="json-brackets ${type == 'array' ? 'json-square-brackets' : 'json-curly-brackets'}">`;
  120. html += type === 'array' ? '[</span><ol class="json-array">' : '{</span><ul class="json-object">';
  121. for (var key in json) {
  122. if (json.hasOwnProperty(key)) {
  123. let comma = --len > 0 ? ',' : '',
  124. jsonPath = parentPath + '.' + key,
  125. collapse = isCollapsable(json[key]) ? '<a href class="json-toggle"></a>' : '',
  126. res = json2html(json[key], jsonPath)
  127. let toHtml = type === 'array' ? res : `<span class="json-key">"${key}"</span>: ${res}`
  128. html += [`<li json-path="${jsonPath}">`, collapse, toHtml, comma, '</li>'].join('')
  129. }
  130. }
  131.  
  132. if (type === 'array') {
  133. html += `</ol><span class="json-brackets json-square-brackets">]</span>`
  134. } else {
  135. html += `</ul><span class="json-brackets json-curly-brackets">}</span>`
  136. }
  137. } else {
  138. html += `<span class="json-brackets ${type == 'array' ? 'json-square-brackets' : 'json-curly-brackets'}">`
  139. html += (type === 'array') ? '[]' : '{}'
  140. html += '</span>'
  141. }
  142. break
  143. default:
  144. /* Escape tags */
  145. json = type === 'string' ? json.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;') : json
  146. if (Utils.isUrl(json)) {
  147. html += `<a target="_blank" href="${json}" class="json-string">"${json}"</a>`;
  148. } else {
  149. json = type === 'string' ? `"${json}"` : json
  150. html += `<span class="json-${type}">${json}</span>`;
  151. }
  152. break
  153. }
  154. return html;
  155. }
  156.  
  157. $.fn.jsonViewer = function (json, jsonpFn) {
  158. return this.each(function () {
  159. /* Transform to HTML */
  160. var html = json2html(json);
  161. /** is JSONP */
  162. if (jsonpFn !== undefined && jsonpFn !== null) {
  163. html = `<div class="jsonp">${jsonpFn}(</div>${html}<div class="jsonp">)</div>`
  164. }
  165. /* Insert HTML in target DOM element */
  166. $(this).html(html);
  167.  
  168. /* Bind click on toggle buttons */
  169. $(this).off('click');
  170. $(this).on('click', 'a.json-toggle', function () {
  171. var target = $(this).toggleClass('collapsed').siblings('ul.json-object, ol.json-array');
  172. target.toggle();
  173. if (target.is(':visible')) {
  174. target.siblings('.json-placeholder').remove();
  175. } else {
  176. var count = target.children('li:not([class*="hidden"])').length;
  177. var placeholder = count + (count > 1 ? ' items' : ' item');
  178. target.after('<a href class="json-placeholder">' + placeholder + '</a>');
  179. }
  180. return false;
  181. });
  182.  
  183. /* Simulate click on toggle button when placeholder is clicked */
  184. $(this).on('click', 'a.json-placeholder', function () {
  185. $(this).siblings('a.json-toggle').click();
  186. $(this).siblings('a.json-placeholder').remove();
  187. return false;
  188. });
  189. });
  190. };
  191. })(_jQuery);
  192. // jquery.json-viewer 插件 结束
  193.  
  194. (function ($) {
  195. let docType = [
  196. "application/vnd.api+json",
  197. "application/javascript",
  198. "application/json",
  199. "text/javascript",
  200. "text/plain",
  201. "text/json",
  202. ], contentType = document.contentType
  203. if (!docType.includes(contentType)) {
  204. return
  205. }
  206.  
  207. var source = $('pre').first();
  208. if (source.length === 0) {
  209. let text = document.body.innerText
  210. if (!Utils.isJSON(text)) {
  211. return
  212. }
  213.  
  214. let pre = document.createElement('pre')
  215. pre.innerText = text
  216. document.body.insertAdjacentHTML('afterbegin', pre);
  217. source = $(pre)
  218. }
  219.  
  220. let rawText = source.text()
  221. if (!rawText) {
  222. return
  223. }
  224.  
  225. // 判断是否为jsonp格式
  226. window.globalJSONPFun = null
  227. let tokens = rawText.match(/^([^\s(]*)\s*\(([\s\S]*)\)\s*;?$/)
  228. if (tokens && tokens[1] && tokens[2]) {
  229. globalJSONPFun = tokens[1]
  230. rawText = tokens[2]
  231. }
  232.  
  233. if (!Utils.isJSON(rawText)) {
  234. return
  235. }
  236.  
  237. // 添加样式
  238. GM_addStyle(GM_getResourceText('swalStyle'))
  239. $("head").append('<link href="//unpkg.com/layui@2.7.6/dist/css/layui.css" rel="stylesheet">')
  240. .append('<script src="//unpkg.com/layui@2.7.6/dist/layui.js">')
  241.  
  242. GM_addStyle(`
  243. :root{
  244. --ft12: 12px;
  245. --ft13: 13px;
  246. --ft14: 14px;
  247. --color-e9: #e9e9e9;
  248. --color-gray: #CCC;
  249. --tab-color: #ECECEC;
  250. }
  251. body, html{
  252. margin: 0;
  253. padding: 0;
  254. font-size: var(--ft14);
  255. }
  256. td{
  257. font-size: var(--ft14);
  258. }
  259. li::marker {
  260. content: '';
  261. }
  262. input:focus, select:focus {
  263. outline: 0;
  264. }
  265. .hidden{
  266. display: none !important;
  267. }
  268. .jsonp{
  269. color: #93983A;
  270. }
  271. .dark .jsonp{
  272. color: #F1D700;
  273. }
  274. .scroll-top{
  275. width: 48px;
  276. height: 48px;
  277. z-index: 999;
  278. position: fixed;
  279. right: 30px;
  280. bottom: 30px;
  281. display: none;
  282. 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=)
  283. }
  284. /** 工具栏样式 START **/
  285. .flex-container{
  286. z-index: 10;
  287. position: fixed;
  288. width: 100vw;
  289. height: 100vh;
  290. display: flex;
  291. flex-direction: column;
  292. }
  293. .tabs, .toolbar{
  294. display: flex;
  295. line-height: 28px;
  296. background-color: var(--tab-color);
  297. border-bottom: 1px solid var(--color-gray);
  298. }
  299. .toolbar{
  300. line-height: 23px;
  301. }
  302. .searchbox{
  303. display: flex;
  304. flex-grow: 1;
  305. }
  306. .toolbar input{
  307. flex-grow: 1;
  308. border: none;
  309. outline: none;
  310. font-size: var(--ft12);
  311. padding-left: 23px;
  312. background-size: 12px;
  313. background-repeat: no-repeat;
  314. background-position: 7px center;
  315. background-image: url(data:image/svg+xml;base64,PCEtLSBUaGlzIFNvdXJjZSBDb2RlIEZvcm0gaXMgc3ViamVjdCB0byB0aGUgdGVybXMgb2YgdGhlIE1vemlsbGEgUHVibGljCiAgIC0gTGljZW5zZSwgdi4gMi4wLiBJZiBhIGNvcHkgb2YgdGhlIE1QTCB3YXMgbm90IGRpc3RyaWJ1dGVkIHdpdGggdGhpcwogICAtIGZpbGUsIFlvdSBjYW4gb2J0YWluIG9uZSBhdCBodHRwOi8vbW96aWxsYS5vcmcvTVBMLzIuMC8uIC0tPgo8c3ZnIGZpbGw9InJnYmEoMTM1LCAxMzUsIDEzNywgMC45KSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgMTIgMTIiPgogIDxwYXRoIGZpbGw9ImNvbnRleHQtZmlsbCIgb3BhY2l0eT0iLjQiIGQ9Ik01IDkuMmwyIDEuNlY2LjFMOC41NSA0aC01LjFMNSA2LjF2My4xeiIvPgogIDxwYXRoIGZpbGw9ImNvbnRleHQtZmlsbCIgZD0iTTEuMTggMi42QTEgMSAwIDAgMSAyIDFIMTBhMSAxIDAgMCAxIC44IDEuNkw4IDYuNHY0LjgyYzAgLjYzLS43Mi45OC0xLjIyLjZsLTIuNS0xLjk5QS43NS43NSAwIDAgMSA0IDkuMjVWNi40MUwxLjE4IDIuNnpNMiAyTDUgNi4wOXYzLjA0bDIgMS41OVY2LjA5TDEwLjAxIDJIMnoiLz4KPC9zdmc+Cg==);
  316. }
  317. .clear {
  318. flex: 0 0 auto;
  319. align-self: center;
  320. margin: 0 4px;
  321. padding: 0;
  322. border: 0;
  323. width: 16px;
  324. height: 16px;
  325. background-color: transparent;
  326. background-image: url(data:image/svg+xml;base64,PCEtLSBUaGlzIFNvdXJjZSBDb2RlIEZvcm0gaXMgc3ViamVjdCB0byB0aGUgdGVybXMgb2YgdGhlIE1vemlsbGEgUHVibGljCiAgIC0gTGljZW5zZSwgdi4gMi4wLiBJZiBhIGNvcHkgb2YgdGhlIE1QTCB3YXMgbm90IGRpc3RyaWJ1dGVkIHdpdGggdGhpcwogICAtIGZpbGUsIFlvdSBjYW4gb2J0YWluIG9uZSBhdCBodHRwOi8vbW96aWxsYS5vcmcvTVBMLzIuMC8uIC0tPgo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiB2aWV3Qm94PSIwIDAgMTYgMTYiIGZpbGw9ImNvbnRleHQtZmlsbCIgZmlsbC1vcGFjaXR5PSJjb250ZXh0LWZpbGwtb3BhY2l0eSI+CiAgPHBhdGggZD0iTTYuNTg2IDhsLTIuMjkzIDIuMjkzYTEgMSAwIDAgMCAxLjQxNCAxLjQxNEw4IDkuNDE0bDIuMjkzIDIuMjkzYTEgMSAwIDAgMCAxLjQxNC0xLjQxNEw5LjQxNCA4bDIuMjkzLTIuMjkzYTEgMSAwIDEgMC0xLjQxNC0xLjQxNEw4IDYuNTg2IDUuNzA3IDQuMjkzYTEgMSAwIDAgMC0xLjQxNCAxLjQxNEw2LjU4NiA4ek04IDBhOCA4IDAgMSAxIDAgMTZBOCA4IDAgMCAxIDggMHoiLz4KPC9zdmc+Cg==);
  327. }
  328. .tabs-item{
  329. border-width: 3px;
  330. border-top: 3px solid var(--tab-color);
  331. }
  332. .tabs-item:hover{
  333. border-top-color: #c3c3c6;
  334. }
  335. .tabs-item, .toolbar-item{
  336. cursor: pointer;
  337. padding: 0 10px;
  338. font-size: var(--ft12);
  339. }
  340. .tabs-item.active{
  341. color: #0060df;
  342. border-top-color: #0060df;
  343. background-color: var(--color-e9);
  344. }
  345. .tabs-item:hover, .toolbar-item:hover{
  346. // background-color: var(--color-e9);
  347. background-color: #D4D4D4
  348. }
  349. /** 工具栏样式 END **/
  350. /** JSON 格式化样式 START **/
  351. ul.json-object,
  352. ul.json-array {
  353. margin: 0 0 0 2px;
  354. list-style-type: none;
  355. border-left: 1px dotted #5D6D7E;
  356. padding-left: 24px;
  357. }
  358. .json-brackets {
  359. font-weight: 700;
  360. }
  361. .json-key {
  362. color: #910F93;
  363. cursor: pointer;
  364. }
  365. .json-string, .json-string a{
  366. color: #4B8A4C;
  367. }
  368. .json-number{
  369. color: #1a01cc;
  370. }
  371. .json-boolean{
  372. color: #905;
  373. }
  374. .json-null {
  375. color: #0031BC;
  376. }
  377. a.json-toggle {
  378. position: rElative;
  379. color: inherit;
  380. opacity: 0.2;
  381. text-decoration: none;
  382. }
  383. a.json-toggle:hover {
  384. opacity: 0.35;
  385. }
  386. a.json-toggle:active {
  387. opacity: 0.5;
  388. }
  389. a.json-toggle:focus {
  390. outline: none;
  391. }
  392. a.json-toggle:before {
  393. top: 2.5px;
  394. left: -15px;
  395. position: absolute;
  396. content: "";
  397. display: block;
  398. width: 0;
  399. height: 0;
  400. border-style: solid;
  401. border-width: 5px 0 5px 8px;
  402. border-color: transparent transparent transparent currentColor;
  403. transform: rotate(90deg);
  404. }
  405. a.json-toggle.collapsed:before {
  406. transform: rotate(0deg);
  407. }
  408. a.json-placeholder {
  409. color: var(--color-gray);
  410. font-size: var(--ft12);
  411. padding: 0 1em;
  412. text-decoration: none;
  413. }
  414. a.json-placeholder:hover {
  415. text-decoration: underline;
  416. }
  417. .json-curly-brackets {
  418. color: #6D9331;
  419. }
  420. .json-square-brackets{
  421. color: #8E9331;
  422. }
  423. /**浅色主题 START **/
  424. .light .json-key {
  425. color: #0451A5;
  426. }
  427. .light .json-string, .light .json-string a{
  428. color: #A31515;
  429. }
  430. .light .json-number{
  431. color: #0B7500;
  432. }
  433. .light .json-boolean{
  434. color: #0000FF;
  435. }
  436. .light .json-null {
  437. color: #0055FF;
  438. }
  439. /**浅色主题 END **/
  440. /** 暗黑主题 START **/
  441. body.dark{
  442. background-color: #333333;
  443. }
  444. .dark li, .dark pre{
  445. color: var(--color-gray);
  446. }
  447. .dark .json-toggle {
  448. opacity: 0.35;
  449. }
  450. .dark .json-toggle:hover {
  451. opacity: 0.5;
  452. }
  453. .dark .json-curly-brackets {
  454. color: #CE70D6;
  455. }
  456. .dark .json-square-brackets{
  457. color: #F1D700;
  458. }
  459. .dark .json-key {
  460. color: #9CDCFE;
  461. }
  462. .dark .json-string, .dark .json-string a{
  463. color: #CE9178;
  464. }
  465. .dark .json-number{
  466. color: #B5CEA8;
  467. }
  468. .dark .json-boolean{
  469. color: #358CD6;
  470. }
  471. .dark .json-null {
  472. color: #569CD6;
  473. }
  474. .dark jmnode {
  475. color: #7CDCFE !important;
  476. }
  477. /** 暗黑主题 END **/
  478. /** JSON 格式化样式 END **/
  479. /** 脑图样式 START **/
  480. #jmContainer{
  481. width: 100vw;
  482. height: calc(100vh - 57px);
  483. }
  484. jmnode{
  485. display: flex;
  486. align-items: center;
  487. padding: 0 7px 0 22px;
  488. }
  489. jmnode{
  490. color: #475872 !important;
  491. box-shadow: none !important;
  492. background-color: transparent !important;
  493. }
  494. jmnode:hover{
  495. text-shadow: 0px 0px 1px currentColor;
  496. }
  497. jmnode.root {
  498. padding: 0;
  499. color: transparent !important;
  500. }
  501. jmnode:not(.root)::before, jmnode.root::before{
  502. content: " ";
  503. top: 50%;
  504. position: absolute;
  505. border-radius: 50%;
  506. transform: translateY(-50%);
  507. }
  508. jmnode:not(.root)::before{
  509. left: 0;
  510. width: 15px;
  511. height: 15px;
  512. background-color: ${Utils.rgbaColor(0.5)};
  513. }
  514. jmnode.root::before{
  515. left: 50%;
  516. width: 18px;
  517. height: 18px;
  518. transform: translate(-18px, -50%);
  519. background-color: ${Utils.rgbaColor(0.5)};
  520. }
  521. jmexpander{
  522. margin-top: 1px;
  523. line-height: 9px;
  524. background-color: #dfdfdf;
  525. }
  526. .mind-array{
  527. opacity: 0.6;
  528. font-size: var(--ft12);
  529. padding-left: 5px;
  530. }
  531. /** 脑图样式 END **/
  532. /** 容器样式 START **/
  533. .tabs-container{
  534. overflow: auto;
  535. line-height: 1.4;
  536. font-family: monospace;
  537. }
  538. .tabs-container > div{
  539. display: none;
  540. }
  541. .tabs-container > div.active{
  542. display: block;
  543. }
  544. .tabs-container #formatContainer{
  545. padding: 10px;
  546. }
  547. .tabs-container #rawTextContainer{
  548. padding: 0 10px;
  549. }
  550. .tabs-container #rawTextContainer pre{
  551. display: block !important;
  552. overflow-wrap: break-word;
  553. white-space: pre-wrap;
  554. }
  555. /** 容器样式 END **/
  556. .layui-layer-tips{
  557. width: auto !important;
  558. }
  559. .tabs .selectbox{
  560. position: absolute;
  561. right: 200px;
  562. display: flex;
  563. font-size: var(--ft13);
  564. }
  565. /** 表格 START **/
  566. table {
  567. width: -webkit-fill-available;
  568. margin-left: 20px;
  569. border-collapse: collapse;
  570. }
  571. table tr:hover{
  572. background-color: #f0f9fe;
  573. }
  574. .dark table tr:hover{
  575. background-color: #353B48
  576. }
  577. table tr.selected td, table tr.selected td a{
  578. color: #fff !important;
  579. background-color: #3875d7;
  580. }
  581. table tbody tr td:first-child{
  582. width: 120px;
  583. }
  584. .simple-tree-table-icon{
  585. color: #000;
  586. opacity: 0.2;
  587. width: 0 !important;
  588. margin: 0 !important;
  589. line-height: 0 !important;
  590. }
  591. .simple-tree-table-icon:hover {
  592. opacity: 0.35;
  593. }
  594. .simple-tree-table-icon:active {
  595. opacity: 0.5;
  596. }
  597. .simple-tree-table-icon:after{
  598. content: "" !important;
  599. }
  600. .simple-tree-table-icon:before {
  601. top: 0.5px;
  602. left: -15px;
  603. position: relative;
  604. content: "";
  605. width: 0;
  606. height: 0;
  607. display: none;
  608. border-style: solid;
  609. border-width: 5px 0 5px 8px;
  610. border-color: transparent transparent transparent currentColor;
  611. transform: rotate(90deg);
  612. }
  613. .simple-tree-table-opened .simple-tree-table-icon:before {
  614. display: block;
  615. }
  616. .simple-tree-table-closed .simple-tree-table-icon:before {
  617. display: block;
  618. transform: rotate(0deg);
  619. }
  620. .dark .simple-tree-table-icon{
  621. color: #FFF;
  622. opacity: 0.5;
  623. }
  624. .tree-len{
  625. color: var(--color-gray);
  626. font-size: var(--ft13);
  627. }
  628. textarea:focus {
  629. outline: 0;
  630. }
  631. .inputJson, .fetchApi{
  632. cursor: pointer;
  633. color: #0060df;
  634. margin-left: 15px;
  635. }
  636. /** 表格 END **/
  637. .httpRequest{
  638. padding: 20px;
  639. }
  640. .httpRequest input,
  641. .httpRequest select {
  642. border-radius: 0;
  643. padding-left: 10px;
  644. border: 1px solid var(--color-gray);
  645. }
  646. .requestbox,
  647. .textarea {
  648. width: 700px;
  649. display: flex;
  650. }
  651. .requestbox {
  652. height: 35px;
  653. margin-bottom: 15px;
  654. }
  655. .textarea input {
  656. flex-grow: 1;
  657. height: 30px;
  658. }
  659. .requestbox input {
  660. flex-grow: 1;
  661.  
  662. }
  663. .requestbox button {
  664. cursor: pointer;
  665. padding: 0 15px;
  666. border: 1px solid var(--color-gray);
  667. }
  668. .requestbox button:active {
  669. background-color: #cfcfcf;
  670. }
  671. `)
  672.  
  673. source.hide()
  674. // 将内容用eval函数处理下
  675. try {
  676. window.globalJSON = eval(`(${rawText})`);
  677. } catch (e) {
  678. window.globalJSON = JSON.parse(rawText);
  679. }
  680.  
  681. $("body").addClass('dark').append(`
  682. <div class="scroll-top"></div>
  683. <div class="flex-container">
  684. <div class="panel">
  685. <div class="tabs">
  686. <div class="tabs-item btn active" id="format">JSON格式化</div>
  687. <div class="tabs-item btn" id="viewJsonMind">JSON脑图</div>
  688. <div class="tabs-item btn" id="viewRawText">原始数据</div>
  689. <div class="selectbox">
  690. <div class="formatStyle">
  691. <label>风格:</label>
  692. <select>
  693. <option value="default">默认</option>
  694. <option value="treaTable">表格</option>
  695. </select>
  696. </div>
  697. <div class="theme" style="margin: 0 15px">
  698. <label>主题: </label>
  699. <select>
  700. <option value="default">默认</option>
  701. <option value="light">浅色</option>
  702. <option value="dark">暗黑</option>
  703. </select>
  704. </div>
  705. <span class="inputJson">JSON 输入</span>
  706. <span class="fetchApi">HTTP 请求</span>
  707. </div>
  708. </div>
  709. <div class="toolbar">
  710. <div class="toolbar-item btn" id="saveJson">保存</div>
  711. <div class="toolbar-item btn" id="copyJson">复制</div>
  712. <div class="toolbar-item btn" id="collapseAll">全部折叠</div>
  713. <div class="toolbar-item btn" id="expandAll">全部展开</div>
  714. <div class="toolbar-item btn" id="jsoncrack" style="display: none;">JSON Crack</div>
  715. <div class="toolbar-item btn" id="beautify" style="display: none;">美化输出</div>
  716. <div class="searchbox">
  717. <input type="text" placeholder="过滤 JSON "/>
  718. <button class="clear" hidden></button>
  719. </div>
  720. </div>
  721. </div>
  722. <div class="tabs-container">
  723. <div class="active" id="formatContainer"></div>
  724. <div id="jmContainer"></div>
  725. <div id="rawTextContainer"><pre></pre></div>
  726. </div>
  727. </div>`)
  728.  
  729. let btnEvent = {
  730. firstFormat: true,
  731. isBeautify: false,
  732. $rawText: $('#rawTextContainer'),
  733. /**
  734. * 保存为文件
  735. */
  736. download: {
  737. download: function (content, filename) {
  738. const link = document.createElement("a")
  739. link.href = content
  740. link.download = filename
  741. link.click()
  742. },
  743. saveJSON: function (text) {
  744. // 创建一个 Blob 对象,包含要下载的文本内容
  745. const blob = new Blob([text], { type: "text/plain;charset=utf-8" });
  746. const url = URL.createObjectURL(blob)
  747. let filename = new Date().getTime() + '.json';
  748. this.download(url, filename)
  749. URL.revokeObjectURL(url);
  750. },
  751. savePNG: () => jm.shoot(),
  752. },
  753. saveJson: function () {
  754. if ($('#jmContainer').is(':visible')) {
  755. this.download.savePNG()
  756. } else {
  757. this.download.saveJSON(this.$rawText.text())
  758. }
  759. },
  760. // 复制JSON文本内容
  761. copyJson: function () {
  762. GM_setClipboard(this.$rawText.text())
  763. layer.msg('复制成功', { time: 1500 })
  764. },
  765. // 全部折叠
  766. collapseAll: function () {
  767. if ($('#formatContainer').is(':visible')) {
  768. try {
  769. $('a.json-toggle').not('.collapsed').click()
  770. } catch (e) { }
  771. } else {
  772. jm.collapse_all()
  773. }
  774. },
  775. // 全部展开
  776. expandAll: function () {
  777. if ($('#formatContainer').is(':visible')) {
  778. try {
  779. $('a.json-placeholder').click().remove()
  780. } catch (e) { }
  781. } else {
  782. jm.expand_all()
  783. jm.scroll_node_to_center(jm.get_root())
  784. }
  785. },
  786. format: function () { },
  787. // 显示JSON脑图
  788. viewJsonMind: function () {
  789. jsonMind.init(globalJSON)
  790. jm.scroll_node_to_center(jm.get_root())
  791. },
  792. // 查看原始JSON内容
  793. viewRawText: function () {
  794. if (this.firstFormat) {
  795. this.$rawText.html(source.clone())
  796. this.firstFormat = false
  797. }
  798. },
  799. // 美化
  800. beautify: function () {
  801. this.isBeautify = !this.isBeautify
  802. if (this.isBeautify) {
  803. let str = JSON.stringify(globalJSON, null, 2)
  804. if (globalJSONPFun !== undefined && globalJSONPFun !== null) {
  805. str = `${globalJSONPFun}(${str})`
  806. }
  807. this.$rawText.find('pre').text(str)
  808. } else {
  809. this.$rawText.html(source.clone())
  810. }
  811. },
  812. jsoncrack: function () {
  813. layer.closeAll()
  814. const index = layer.open({
  815. type: 1,
  816. title: false,
  817. maxmin: true,
  818. shadeClose: true,
  819. area: ['900px', '600px'],
  820. content: '<iframe style="width: 100%;height: 100%;border: 0;" id="jsoncrackEmbed" src="https://jsoncrack.feny.ink/widget"></iframe>',
  821. success: function (layero) {
  822. const jsonCrackEmbed = layero.find('#jsoncrackEmbed')[0]
  823. window?.addEventListener("message", () => {
  824. console.log(globalJSON)
  825. jsonCrackEmbed.contentWindow.postMessage({
  826. json: JSON.stringify(globalJSON)
  827. }, "*");
  828. });
  829. }
  830. });
  831. layer.full(index);
  832. },
  833. init: function () {
  834. this.viewRawText()
  835. // 按钮点击事件
  836. $('.btn').click(e => {
  837. const target = e.target, id = target.id
  838. if (target.classList.contains('tabs-item')) {
  839. let index = $(target).index()
  840. $(target).addClass('active').siblings().removeClass("active")
  841. $('.tabs-container > div').removeClass("active").eq(index).addClass('active')
  842.  
  843. let beautifyEl = $('#beautify'),
  844. searchEl = $('.searchbox'),
  845. copyJsonEl = $('#copyJson'),
  846. jsoncrackEl = $('#jsoncrack'),
  847. aEl = $('#collapseAll, #expandAll')
  848. id === 'format' ? searchEl.show() : searchEl.hide()
  849. id === 'viewJsonMind' ? copyJsonEl.hide() && jsoncrackEl.show() : copyJsonEl.show() && jsoncrackEl.hide()
  850. id === 'viewRawText' ? (beautifyEl.show() && aEl.hide()) : (beautifyEl.hide() && aEl.show())
  851. }
  852. this[id](target)
  853. })
  854.  
  855. return this
  856. }
  857. },
  858. jsonMind = {
  859. isFirst: true,
  860. // JSON数据转换为jsMind所需要的数据结构
  861. convert: function (json) {
  862. let children = []
  863. if (typeof json === 'object') {
  864. for (let key in json) {
  865. let val = json[key],
  866. isArray = Array.isArray(val)
  867.  
  868. if (isArray && val.length > 0) {
  869. val = Utils.findMaxKeysObject(val)
  870. }
  871.  
  872. children.push({
  873. isArray,
  874. chain: key,
  875. id: key + '_' + Math.random(),
  876. topic: `${key}<span class="mind-array">${Utils.getType(json[key])}</span>`,
  877. children: this.convert(val)
  878. })
  879. }
  880. }
  881. return children;
  882. },
  883. // 脑图节点调用链
  884. mindChain: function (node) {
  885. let chain = node.data.chain
  886. if (!node.parent) {
  887. return chain
  888. }
  889.  
  890. let parent = node.parent, parentChain = this.mindChain(parent)
  891. chain = parent.data.isArray ? `${parentChain}[0].${chain}` : `${parentChain}.${chain}`
  892. return chain
  893. },
  894. // 显示脑图
  895. show: function (json) {
  896. let isArr = Array.isArray(json);
  897. if (isArr) {
  898. if (typeof json[0] !== 'object') {
  899. layer.msg('数据结构无法生成脑图', { time: 1000 })
  900. return this
  901. }
  902. json = json[0]
  903. }
  904.  
  905. if (!this.isFirst) {
  906. return this
  907. }
  908.  
  909. jm.show({
  910. "meta": {
  911. "name": "JSON脑图",
  912. "author": "1220301855@qq.com",
  913. "version": "1.0"
  914. },
  915. "format": "node_tree",
  916. /* 数据内容 */
  917. "data": {
  918. "id": "root",
  919. "topic": 'Response',
  920. "direction": "left",
  921. "children": this.convert(json),
  922. "chain": isArr ? 'Response[0]' : 'Response'
  923. }
  924. })
  925. this.isFirst = false
  926. return this
  927. },
  928. // 脑图节点事件
  929. event: function () {
  930. $("jmnode").on('dblclick mouseover mouseout', function (event) {
  931. let that = $(this), node = jm.get_node(that.attr('nodeid'))
  932. if (!node.parent) {
  933. return
  934. }
  935.  
  936. switch (event.type) {
  937. case 'dblclick':
  938. GM_setClipboard(jsonMind.mindChain(node))
  939. layer.msg('节点路径复制成功', { time: 1500 })
  940. break;
  941. case 'mouseover':
  942. let s = `<b>节点路径(双击复制)</b><br/>${jsonMind.mindChain(node)}`
  943. layer.tips(s, that, {
  944. time: 0,
  945. tips: [2, '#2e59a7']
  946. });
  947. break;
  948. default:
  949. layer.closeAll()
  950. break;
  951. }
  952. })
  953. return this
  954. },
  955. collapseOrExpand: function () {
  956. $("jmnode").on('click', function () {
  957. let node = jm.get_node($(this).attr('nodeid'))
  958. jm.toggle_node(node)
  959. })
  960. return this
  961. },
  962. init: function (json) {
  963. if (!window.jm) {
  964. window.jm = new jsMind({
  965. mode: 'side',
  966. editable: false,
  967. container: 'jmContainer',
  968. view: {
  969. hmargin: 50, // 思维导图距容器外框的最小水平距离
  970. vmargin: 50, // 思维导图距容器外框的最小垂直距离
  971. engine: 'svg', // 思维导图各节点之间线条的绘制引擎
  972. draggable: true, // 当容器不能完全容纳思维导图时,是否允许拖动画布代替鼠标滚动
  973. support_html: false,
  974. line_color: '#C4C9D0',
  975. },
  976. zoom: { // 配置缩放
  977. min: 0.1, // 最小的缩放比例
  978. max: 2.1, // 最大的缩放比例
  979. step: 0.1, // 缩放比例间隔
  980. },
  981. layout: {
  982. vspace: 7, // 节点之间的垂直间距
  983. hspace: 150, // 节点之间的水平空间
  984. },
  985. });
  986. }
  987.  
  988. this.show(json).event().collapseOrExpand()
  989. }
  990. },
  991. otherEvent = {
  992. // 过滤 JSON
  993. filterJSON: function (filter) {
  994. let style = GM_getValue('formatStyle') || 'default'
  995. if (!filter) {
  996. style == 'default' ? $('#formatContainer li').removeClass('hidden') : $('.json-key').parent().removeClass('hidden')
  997. return
  998. }
  999.  
  1000. let chainSet = new Set()
  1001. /**
  1002. * 模糊匹配 JSON key
  1003. * 假如`filter`值为`id`, querySelectorAll得到DOM节点
  1004. * 得到:['.feedList.0.images.0.user_id', '.feedList.0.images.0', '.feedList.0.images', '.feedList.0', '.feedList']
  1005. */
  1006. document.querySelectorAll('#formatContainer *[json-path]').forEach(el => {
  1007. let chain = $(el).attr('json-path')
  1008. if (!chain) {
  1009. return
  1010. }
  1011. let newChain = chain.substr(chain.lastIndexOf('.'))
  1012. if (!newChain.toLowerCase().includes(filter.toLowerCase())) {
  1013. return
  1014. }
  1015. chainSet.add(chain)
  1016. while (chain = chain.substr(0, chain.lastIndexOf('.'))) {
  1017. chainSet.add(chain)
  1018. }
  1019. })
  1020.  
  1021. /**
  1022. * 模糊匹配 JSON value
  1023. */
  1024. document.querySelectorAll("#formatContainer *[class*='json-']:not([class*='json-key']):not([class*='json-brackets'])")
  1025. .forEach(el => {
  1026. let target = $(el),
  1027. chain = style == 'default' ? target.parent().attr('json-path') : target.siblings('.json-key').attr('json-path')
  1028. if (!chain) {
  1029. return
  1030. }
  1031. let text = target.text()
  1032. if (!text.toLowerCase().includes(filter.toLowerCase())) {
  1033. return
  1034. }
  1035. chainSet.add(chain)
  1036. while (chain = chain.substr(0, chain.lastIndexOf('.'))) {
  1037. chainSet.add(chain)
  1038. }
  1039. })
  1040. // console.log(chainSet)
  1041. style == 'default' ? $('#formatContainer li').addClass('hidden') : $('.json-key').parent().addClass('hidden')
  1042. chainSet.forEach(chain => {
  1043. style == 'default' ? $(`#formatContainer *[json-path="${chain}"]`).removeClass('hidden')
  1044. : $(`#formatContainer *[json-path="${chain}"]`).parent().removeClass('hidden')
  1045. })
  1046. },
  1047. // JSON 过滤
  1048. input: function () {
  1049. let that = this
  1050. $('input').on('input', function () {
  1051. let val = $(this).val()
  1052. val === '' ? $('.clear').attr('hidden', true) : $('.clear').attr('hidden', false)
  1053. that.filterJSON(val)
  1054. })
  1055. return that
  1056. },
  1057. // 清空输入框内容
  1058. clear: function () {
  1059. let that = this
  1060. $('.clear').click(function () {
  1061. that.filterJSON()
  1062. $('input').val('')
  1063. $(this).attr('hidden', true)
  1064. })
  1065. return this
  1066. },
  1067. // 返回顶部
  1068. scrollTop: function () {
  1069. $('.scroll-top').click(function () {
  1070. $('.tabs-container').animate({
  1071. scrollTop: '0'
  1072. }, 1000);
  1073. })
  1074. $('.tabs-container').scroll(function (e) {
  1075. let scrollTop = $(this).scrollTop()
  1076. scrollTop > 500 ? $('.scroll-top').fadeIn() : $('.scroll-top').fadeOut()
  1077. });
  1078. return this
  1079. },
  1080. // 所有a标签,看是否是图片,是图片生成预览图
  1081. urlHover: function () {
  1082. $("#formatContainer").on({
  1083. mouseenter: function () {
  1084. var that = $(this),
  1085. href = that.attr('href')
  1086. if (Utils.isImg(href)) {
  1087. layer.tips(`<img src="${href}" />`, that, {
  1088. time: 0,
  1089. anim: 5,
  1090. maxWidth: 500,
  1091. tips: [3, '#d9d9d9']
  1092. });
  1093. }
  1094. },
  1095. mouseleave: () => layer.closeAll()
  1096. }, 'a[href]')
  1097. return this
  1098. },
  1099. // 提示key的JSONPath
  1100. tipsJsonPath: function () {
  1101. var that = this
  1102. $("#formatContainer").on({
  1103. mouseenter: function () {
  1104. let jsonPath = that.getJsonPath(this)
  1105. let tips = `<b>ctrl + 点击复制</b><br/>${jsonPath}`
  1106. layer.tips(tips, this, {
  1107. time: 0,
  1108. anim: 5,
  1109. maxWidth: 500,
  1110. tips: [1, '#2e59a7']
  1111. })
  1112. },
  1113. mouseleave: () => layer.closeAll()
  1114. }, '.json-key')
  1115. return this
  1116. },
  1117. // 单击key复制JSONPath
  1118. copyJsonPath: function () {
  1119. var that = this
  1120. $("#formatContainer").on('click', '.json-key', function (event) {
  1121. if (event.ctrlKey) {
  1122. let jsonPath = that.getJsonPath(this)
  1123. GM_setClipboard(jsonPath)
  1124. layer.msg('复制成功', { time: 1500 })
  1125. }
  1126. })
  1127. return this
  1128. },
  1129. getJsonPath: function (element) {
  1130. let style = GM_getValue('formatStyle') || 'default'
  1131. let jsonPath = style == 'default' ? $(element).parent().attr('json-path') : $(element).attr('json-path')
  1132. return jsonPath.split('.').reduce((prev, next) => /^\d+$/.test(next) ? prev + `[${next}]` : prev + '.' + next)
  1133. },
  1134. inputJson: function () {
  1135. let that = this
  1136. $('.inputJson').off('click').click(function () {
  1137. layer.prompt({
  1138. title: "JSON 输入",
  1139. formType: 2,
  1140. shadeClose: true,
  1141. maxlength: 1000000,
  1142. }, function (text) {
  1143. if (!text) {
  1144. layer.msg("内容不能为空", { time: 1500 })
  1145. return
  1146. }
  1147.  
  1148. let rawText = text, oldGlobalJSONPFun = globalJSONPFun
  1149.  
  1150. // 判断是否为jsonp格式
  1151. let match = text.match(/^([^\s(]*)\s*\(([\s\S]*)\)\s*;?$/)
  1152. if (match && match[1] && match[2]) {
  1153. globalJSONPFun = match[1]
  1154. text = match[2]
  1155. }
  1156.  
  1157. try {
  1158. let json = JSON.parse(JSON.stringify(eval(`(${text})`)))
  1159. that.reload(json, rawText)
  1160. } catch (e) {
  1161. console.log(e)
  1162. globalJSONPFun = oldGlobalJSONPFun
  1163. layer.msg("JSON不合法", { time: 1500 })
  1164. }
  1165. })
  1166. })
  1167. return this
  1168. },
  1169. fetchApi: function () {
  1170. let that = this
  1171. $('.fetchApi').off('click').click(function () {
  1172. layer.open({
  1173. type: 1,
  1174. closeBtn: 0, //不显示关闭按钮
  1175. shadeClose: true, //开启遮罩关闭
  1176. title: 'HTTP 请求',
  1177. content: `<form class="httpRequest">
  1178. <div class="requestbox">
  1179. <select name="method">
  1180. <option value="POST">POST</option>
  1181. <option value="GET">GET</option>
  1182. <option value="PUT">PUT</option>
  1183. <option value="DELETE">DELETE</option>
  1184. </select>
  1185. <input name="url" placeholder="请求地址" />
  1186. <select name="contentType">
  1187. <option value="application/x-www-form-urlencoded;charset=UTF-8">urlencoded</option>
  1188. <option value="application/json;charset=UTF-8">application/json</option>
  1189. </select>
  1190. <button type="submit">发送</button>
  1191. </div>
  1192. <div class="textarea">
  1193. <input name="headers" placeholder='请求头 {"token": "test"}' />
  1194. <input name="params" placeholder='请求参数 {"id": "test", ""name": "test"}' />
  1195. </div>
  1196. </form>`
  1197. })
  1198.  
  1199.  
  1200. $('form').submit(function (event) {
  1201. event.preventDefault();
  1202.  
  1203. let serialize = $(this).serializeArray()
  1204. let form = {}
  1205. for (let key in serialize) {
  1206. let it = serialize[key]
  1207. form[it.name] = it.value
  1208. }
  1209.  
  1210. if (form.url === '') {
  1211. layer.msg('请求URL不能为空')
  1212. return
  1213. }
  1214.  
  1215. let headers = form.headers
  1216. let params = form.params
  1217. try {
  1218. if (headers) {
  1219. headers = JSON.parse(headers)
  1220. }
  1221. } catch (e) {
  1222. layer.msg('请求头格式不合法')
  1223. return
  1224. }
  1225. try {
  1226. if (params) {
  1227. params = JSON.parse(params)
  1228. }
  1229. } catch (e) {
  1230. layer.msg('请求参数格式不合法')
  1231. return
  1232. }
  1233.  
  1234. layer.load(0, { shade: false });
  1235. $.ajax({
  1236. url: 'https://fetch-api.feny.ink/httpRequest',
  1237. type: 'POST',
  1238. data: JSON.stringify(form),
  1239. contentType: 'application/json'
  1240. }).then(response => {
  1241. globalJSONPFun = null
  1242. that.reload(response, JSON.stringify(response))
  1243. }, error => {
  1244. layer.closeAll()
  1245. console.log(error);
  1246. })
  1247. })
  1248. })
  1249. return this
  1250. },
  1251. reload: function (json, rawText) {
  1252. globalJSON = json
  1253. source.text(rawText)
  1254. btnEvent.isBeautify = false
  1255. btnEvent.$rawText.html(source.clone())
  1256. formatStyle.setStyle()
  1257. jsonMind.isFirst = true
  1258. jsonMind.init(globalJSON)
  1259. layer.closeAll()
  1260. },
  1261. init: function () {
  1262. this.input().clear().scrollTop().urlHover().tipsJsonPath().copyJsonPath()
  1263. .inputJson().fetchApi()
  1264. }
  1265. },
  1266. theme = {
  1267. // 切换主题
  1268. changeTheme: function () {
  1269. let that = this
  1270. $('.theme select').change(function (e) {
  1271. let val = $(e.target).val()
  1272. GM_setValue('theme', val)
  1273. that.setTheme()
  1274. })
  1275. return this
  1276. },
  1277. // 设置主题
  1278. setTheme: function () {
  1279. let theme = GM_getValue('theme') || 'default'
  1280. $('body').removeClass().addClass(theme)
  1281. $('.theme select').val(theme)
  1282. return this
  1283. },
  1284. init: function () {
  1285. this.setTheme().changeTheme()
  1286. }
  1287. },
  1288. formatStyle = {
  1289. // 切换风格
  1290. changeStyle: function () {
  1291. let that = this
  1292. $('.formatStyle select').change(function (e) {
  1293. let val = $(e.target).val()
  1294. GM_setValue('formatStyle', val)
  1295. that.setStyle()
  1296. })
  1297. return this
  1298. },
  1299. // 设置风格
  1300. setStyle: function () {
  1301. let style = GM_getValue('formatStyle') || 'default'
  1302. $('.formatStyle select').val(style)
  1303.  
  1304. $('input').val('')
  1305. $('#formatContainer').html('')
  1306. if (style === 'default') {
  1307. $('#formatContainer').jsonViewer(globalJSON, globalJSONPFun)
  1308. } else {
  1309. let appendHtml = `<table id="treeTable">${treeTableHtml(globalJSON)}</table>`
  1310. if (globalJSONPFun !== undefined && globalJSONPFun !== null) {
  1311. appendHtml = `<div class="jsonp">${globalJSONPFun}(</div>${appendHtml}<div class="jsonp">)</div>`
  1312. }
  1313. $('#formatContainer').append(appendHtml)
  1314. $('#treeTable').simpleTreeTable({
  1315. expander: '#expandAll',
  1316. collapser: '#collapseAll',
  1317. })
  1318.  
  1319. // Highlight selected row
  1320. $('#treeTable').on("mousedown", "tr", function () {
  1321. $(".selected").not(this).removeClass("selected");
  1322. $(this).toggleClass("selected")
  1323. })
  1324. }
  1325. return this
  1326. },
  1327. init: function () {
  1328. this.setStyle().changeStyle()
  1329. theme.init()
  1330. btnEvent.init()
  1331. setTimeout(() => otherEvent.init(), 1000)
  1332. }
  1333. }
  1334.  
  1335. formatStyle.init()
  1336.  
  1337. /**
  1338. * 表格
  1339. */
  1340. function treeTableHtml(json, level = 0, pId = '', pChain = '') {
  1341. let tr = ''
  1342. for (let key in json) {
  1343. let val = json[key],
  1344. type = Utils.getType(val),
  1345. tId = key + '_' + Math.random(),
  1346. chain = pChain + "." + key
  1347. if (['array', 'object'].includes(type)) {
  1348. let brackets = '',
  1349. len = Object.keys(val).length,
  1350. res = treeTableHtml(val, level + 1, tId, chain)
  1351.  
  1352. if (!res) {
  1353. if (type === 'array') {
  1354. brackets = `<span class="json-brackets json-square-brackets">[]</span>`
  1355. } else {
  1356. brackets = `<span class="json-brackets json-curly-brackets">{}</span>`
  1357. }
  1358. }
  1359.  
  1360. tr += `
  1361. <tr data-node-id="${tId}" data-node-pid="${pId}" type="${type}">
  1362. <td class="json-key" json-path="${chain}" style="padding-left: ${level * 19}px">${key}:
  1363. <span class="tree-len">${len > 0 ? (type === 'array' ? '[' + len + ']' : '{' + len + '}') : ''}</span>
  1364. </td>
  1365. <td>${brackets}</td>
  1366. </tr>`
  1367. tr += res
  1368. } else {
  1369. val = (type === 'string') ? val.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;') : val
  1370. tr += `<tr data-node-id="${tId}" data-node-pid="${pId}" type="${type}">
  1371. <td class="json-key" json-path="${chain}" style="padding-left: ${level * 19}px">${key}:</td>`
  1372. if (Utils.isUrl(val)) {
  1373. tr += `<td class="json-${type}"><a target="_blank" href="${val}">"${val}"</a></td>`
  1374. } else {
  1375. val = (type === 'string') ? `"${val}"` : val
  1376. tr += `<td class="json-${type}">${val}</td>`
  1377. }
  1378. tr += '</tr>'
  1379. }
  1380. }
  1381. return tr;
  1382. }
  1383.  
  1384. })(_jQuery)
  1385.  
  1386.  
  1387. const openInTab = () => GM_openInTab('https://fetch-api.feny.ink/example.json')
  1388. GM_registerMenuCommand('测试JSON( Alt + j )', openInTab);
  1389. document.addEventListener('keydown', function (event) {
  1390. if (event.altKey && event.key === 'j') {
  1391. openInTab()
  1392. }
  1393. })
  1394. })();