JSON Viewer

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

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

  1. // ==UserScript==
  2. // @license MIT
  3. // @name JSON Viewer
  4. // @namespace http://tampermonkey.net/
  5. // @version 0.5.3
  6. // @note v0.5.3 增加暗黑主题色
  7. // @note v0.5.2 单击JSON格式化的key可复制JSONPath
  8. // @note v0.5.1 修复JSONPath提示有误
  9. // @note v0.5.0 解决chrome 120+以上内核JSON格式化不执行和引入layer报错问题
  10. // @note v0.4.9 布局修改,增加保存JSON/脑图为文件,增加JSON过滤,鼠标移入key提示JSONPath
  11. // @note v0.4.8 代码优化
  12. // @note v0.4.7 增加对JSONP的判断,代码优化
  13. // @note v0.4.6 增加复制按钮,JSON脑图CSS样式细节优化,JSON脑图增加收起/展开子节点按钮
  14. // @note v0.4.5 在json-viewer-updated原基础上进行了一些修改,主要有CSS样式修改,新增折叠/展开全部功能,新增JSON脑图功能,脑图节点点击显示调用路径
  15. // @description 格式化显示JSON使数据看起来更加漂亮,支持折叠/展开格式化后的数据,支持JSON脑图让调用层级看着更清晰,支持复制JSON脑图节点路径
  16. // @author Feny
  17. // @match *://*/*
  18. // @grant GM_addStyle
  19. // @grant GM_getValue
  20. // @grant GM_setValue
  21. // @grant unsafeWindow
  22. // @grant GM_setClipboard
  23. // @grant GM_getResourceText
  24. // @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==
  25. // @require https://code.jquery.com/jquery.min.js
  26. // @require https://unpkg.com/jsmind@0.8.5/es6/jsmind.js
  27. // @require https://unpkg.com/dom-to-image@2.6.0/dist/dom-to-image.min.js
  28. // @require https://unpkg.com/jsmind@0.8.5/es6/jsmind.screenshot.js
  29. // @resource swalStyle https://unpkg.com/jsmind@0.8.5/style/jsmind.css
  30. // ==/UserScript==
  31.  
  32.  
  33. (function() {
  34. 'use strict';
  35.  
  36. const Utils = {
  37. // 检查字符串是否为URL
  38. isUrl: function (string) {
  39. var regexp = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/;
  40. return regexp.test(string);
  41. },
  42.  
  43. // 检查是否是图片链接
  44. isImg: function (pathImg) {
  45. // var regexp = /^(http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?\/([\w#!:.?+=&%@!\-\/])*\.(gif|jpg|jpeg|png|GIF|JPG|PNG)([\w#!:.?+=&%@!\-\/])?/;
  46. let regexp = /\.(ico|bmp|gif|jpg|jpeg|png|svg|webp|GIF|JPG|PNG|WEBP|SVG)([\w#!:.?+=&%@!\-\/])?/i
  47. return regexp.test(pathImg);
  48. },
  49. // 检验内容是否是json格式的内容
  50. isJSON: function (str) {
  51. try {
  52. JSON.parse(str)
  53. return true
  54. } catch(e) {
  55. console.log("is not json", e)
  56. return false
  57. }
  58. },
  59. // 获取数据类型
  60. getType: function (value){
  61. return Object.prototype.toString.call(value).match(/\s(.+)]/)[1].toLowerCase();
  62. },
  63. // JSON 过滤
  64. filterJson: function(json, filter) {
  65. if(!filter){
  66. return json
  67. }
  68.  
  69. filter = filter.toLowerCase()
  70. let newJSON = Utils.getType(json) == 'array' ? [] : {}
  71. for (let key in json) {
  72. let val = json[key]
  73. if (typeof val === 'object') {
  74. let subJSON = this.filterJson(val, filter);
  75. if (Object.keys(subJSON).length > 0) {
  76. newJSON[key] = subJSON;
  77. }
  78. }else{
  79. if(key.toLowerCase().includes(filter.toLowerCase())){
  80. newJSON[key] = val
  81. }
  82.  
  83. if(val !== null && val.toString().toLowerCase().includes(filter.toLowerCase())){
  84. newJSON[key] = val
  85. }
  86. }
  87. }
  88. return newJSON;
  89. }
  90. }
  91.  
  92. // jquery.json-viewer 插件 开始
  93. // 解决和原网页jquery版本冲突
  94. var _jQuery = jQuery.noConflict(true);
  95. (function($){
  96. /**
  97. * 检查 arg 是否为至少包含 1 个元素的数组或至少包含 1 个键的字典
  98. */
  99. function isCollapsable(arg) {
  100. return arg instanceof Object && Object.keys(arg).length > 0;
  101. }
  102.  
  103. /**
  104. * 将 JSON 对象转换为 HTML 表示形式
  105. * @return string
  106. */
  107. function json2html(json, parentPath = '') {
  108. let html = '', type = Utils.getType(json)
  109. switch(type){
  110. case 'array':
  111. case 'object':
  112. let len = json.length || Object.keys(json).length;
  113. if (len > 0) {
  114. html += `<span class="json-brackets ${type == 'array' ? 'json-square-brackets' : 'json-curly-brackets'}">`;
  115. html += type === 'array' ? '[</span><ol class="json-array">' : '{</span><ul class="json-object">';
  116. for (var key in json) {
  117. if (json.hasOwnProperty(key)) {
  118. let comma = --len > 0 ? ',' : '',
  119. jsonPath = parentPath + '.' + key,
  120. collapse = isCollapsable(json[key]) ? '<a href class="json-toggle"></a>' : '',
  121. res = json2html(json[key], jsonPath)
  122. let toHtml = type === 'array' ? res : `<span class="json-key">"${key}"</span>: ${res}`
  123. html += [`<li json-path="${jsonPath}">`, collapse, toHtml, comma, '</li>'].join('')
  124. }
  125. }
  126.  
  127. if(type === 'array'){
  128. html += `</ol><span class="json-brackets ${type == 'array' ? 'json-square-brackets' : 'json-curly-brackets'}">]</span>`
  129. }else{
  130. html += `</ul><span class="json-brackets ${type == 'array' ? 'json-square-brackets' : 'json-curly-brackets'}">}</span>`
  131. }
  132. }else{
  133. html += `<span class="json-brackets ${type == 'array' ? 'json-square-brackets' : 'json-curly-brackets'}">`
  134. html += (type === 'array') ? '[]' : '{}'
  135. html += '</span>'
  136. }
  137. break
  138. default:
  139. /* Escape tags */
  140. json = type === 'string' ? json.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;') : json
  141. if (Utils.isUrl(json)){
  142. html += `<a target="_blank" href="${json}" class="json-string">"${json}"</a>`;
  143. }else{
  144. json = type === 'string' ? `"${json}"` : json
  145. html += `<span class="json-${type}">${json}</span>`;
  146. }
  147. break
  148. }
  149. return html;
  150. }
  151.  
  152. $.fn.jsonViewer = function(json, jsonpFun) {
  153. return this.each(function() {
  154. /* Transform to HTML */
  155. var html = json2html(json);
  156. /** is JSONP */
  157. if(jsonpFun !== undefined && jsonpFun !== null){
  158. html = `<div>${jsonpFun}(</div>${html}<div>)</div>`
  159. }
  160. /* Insert HTML in target DOM element */
  161. $(this).html(html);
  162.  
  163. /* Bind click on toggle buttons */
  164. $(this).off('click');
  165. $(this).on('click', 'a.json-toggle', function() {
  166. var target = $(this).toggleClass('collapsed').siblings('ul.json-object, ol.json-array');
  167. target.toggle();
  168. if (target.is(':visible')) {
  169. target.siblings('.json-placeholder').remove();
  170. }else {
  171. var count = target.children('li:not([class*="hidden"])').length;
  172. var placeholder = count + (count > 1 ? ' items' : ' item');
  173. target.after('<a href class="json-placeholder">' + placeholder + '</a>');
  174. }
  175. return false;
  176. });
  177.  
  178. /* Simulate click on toggle button when placeholder is clicked */
  179. $(this).on('click', 'a.json-placeholder', function() {
  180. $(this).siblings('a.json-toggle').click();
  181. $(this).siblings('a.json-placeholder').remove();
  182. return false;
  183. });
  184. });
  185. };
  186. })(_jQuery);
  187. // jquery.json-viewer 插件 结束
  188.  
  189. (function($){
  190. var source = $('pre').first();
  191. if(source.length === 0){
  192. return
  193. }
  194. let rawText = source.text()
  195. if(!rawText){
  196. return
  197. }
  198.  
  199. // 判断是否为jsonp格式
  200. let jsonpFun = null, tokens = rawText.match(/^([^\s(]*)\s*\(([\s\S]*)\)\s*;?$/)
  201. if (tokens && tokens[1] && tokens[2]) {
  202. jsonpFun = tokens[1]
  203. rawText= tokens[2]
  204. }
  205.  
  206. if(!Utils.isJSON(rawText)){
  207. return
  208. }
  209.  
  210. // 随机rgb颜色
  211. let rgbaColor = `${Math.random()*256}, ${Math.random()*256}, ${Math.random()*256}`
  212. // 添加样式
  213. GM_addStyle(GM_getResourceText('swalStyle'))
  214. $("head").append(`<link rel="stylesheet" type="text/css" href="https://unpkg.com/layer-src@3.5.1/dist/theme/default/layer.css"/>`)
  215. .append('<script src="https://unpkg.com/layui@2.9.17/dist/layui.js" type="text/javascript" ></script>')
  216.  
  217. GM_addStyle(`
  218. body, html{
  219. margin: 0;
  220. padding: 0;
  221. font-size: 14px;
  222. }
  223. td{
  224. font-size: 14px;
  225. }
  226. li::marker {
  227. content: '';
  228. }
  229. .hidden{
  230. display: none !important;
  231. }
  232. .scroll-top{
  233. width: 48px;
  234. height: 48px;
  235. z-index: 999;
  236. position: fixed;
  237. right: 30px;
  238. bottom: 30px;
  239. 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=)
  240. }
  241. /** 工具栏样式 START **/
  242. .flex-container{
  243. z-index: 10;
  244. position: fixed;
  245. width: 100vw;
  246. height: 100vh;
  247. display: flex;
  248. flex-direction: column;
  249. }
  250. .tabs, .toolbar{
  251. display: flex;
  252. line-height: 28px;
  253. background: #f3f3f3;
  254. border-bottom: 1px solid #e0e0e2;
  255. }
  256. .toolbar{
  257. line-height: 23px;
  258. }
  259. .searchbox{
  260. display: flex;
  261. flex-grow: 1;
  262. }
  263. .toolbar input{
  264. flex-grow: 1;
  265. border: none;
  266. outline: none;
  267. font-size: 12px;
  268. padding-left: 23px;
  269. background-size: 12px;
  270. background-repeat: no-repeat;
  271. background-position: 7px center;
  272. background-image: url(data:image/svg+xml;base64,PCEtLSBUaGlzIFNvdXJjZSBDb2RlIEZvcm0gaXMgc3ViamVjdCB0byB0aGUgdGVybXMgb2YgdGhlIE1vemlsbGEgUHVibGljCiAgIC0gTGljZW5zZSwgdi4gMi4wLiBJZiBhIGNvcHkgb2YgdGhlIE1QTCB3YXMgbm90IGRpc3RyaWJ1dGVkIHdpdGggdGhpcwogICAtIGZpbGUsIFlvdSBjYW4gb2J0YWluIG9uZSBhdCBodHRwOi8vbW96aWxsYS5vcmcvTVBMLzIuMC8uIC0tPgo8c3ZnIGZpbGw9InJnYmEoMTM1LCAxMzUsIDEzNywgMC45KSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgMTIgMTIiPgogIDxwYXRoIGZpbGw9ImNvbnRleHQtZmlsbCIgb3BhY2l0eT0iLjQiIGQ9Ik01IDkuMmwyIDEuNlY2LjFMOC41NSA0aC01LjFMNSA2LjF2My4xeiIvPgogIDxwYXRoIGZpbGw9ImNvbnRleHQtZmlsbCIgZD0iTTEuMTggMi42QTEgMSAwIDAgMSAyIDFIMTBhMSAxIDAgMCAxIC44IDEuNkw4IDYuNHY0LjgyYzAgLjYzLS43Mi45OC0xLjIyLjZsLTIuNS0xLjk5QS43NS43NSAwIDAgMSA0IDkuMjVWNi40MUwxLjE4IDIuNnpNMiAyTDUgNi4wOXYzLjA0bDIgMS41OVY2LjA5TDEwLjAxIDJIMnoiLz4KPC9zdmc+Cg==);
  273. }
  274. .clear {
  275. flex: 0 0 auto;
  276. align-self: center;
  277. margin: 0 4px;
  278. padding: 0;
  279. border: 0;
  280. width: 16px;
  281. height: 16px;
  282. background-color: transparent;
  283. background-image: url(data:image/svg+xml;base64,PCEtLSBUaGlzIFNvdXJjZSBDb2RlIEZvcm0gaXMgc3ViamVjdCB0byB0aGUgdGVybXMgb2YgdGhlIE1vemlsbGEgUHVibGljCiAgIC0gTGljZW5zZSwgdi4gMi4wLiBJZiBhIGNvcHkgb2YgdGhlIE1QTCB3YXMgbm90IGRpc3RyaWJ1dGVkIHdpdGggdGhpcwogICAtIGZpbGUsIFlvdSBjYW4gb2J0YWluIG9uZSBhdCBodHRwOi8vbW96aWxsYS5vcmcvTVBMLzIuMC8uIC0tPgo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiB2aWV3Qm94PSIwIDAgMTYgMTYiIGZpbGw9ImNvbnRleHQtZmlsbCIgZmlsbC1vcGFjaXR5PSJjb250ZXh0LWZpbGwtb3BhY2l0eSI+CiAgPHBhdGggZD0iTTYuNTg2IDhsLTIuMjkzIDIuMjkzYTEgMSAwIDAgMCAxLjQxNCAxLjQxNEw4IDkuNDE0bDIuMjkzIDIuMjkzYTEgMSAwIDAgMCAxLjQxNC0xLjQxNEw5LjQxNCA4bDIuMjkzLTIuMjkzYTEgMSAwIDEgMC0xLjQxNC0xLjQxNEw4IDYuNTg2IDUuNzA3IDQuMjkzYTEgMSAwIDAgMC0xLjQxNCAxLjQxNEw2LjU4NiA4ek04IDBhOCA4IDAgMSAxIDAgMTZBOCA4IDAgMCAxIDggMHoiLz4KPC9zdmc+Cg==);
  284. }
  285. .tabs-item{
  286. border-top: 2px solid #f3f3f3;
  287. }
  288. .tabs-item:hover{
  289. background: #e9e9e9;
  290. border-top: 2px solid #c3c3c6;
  291. }
  292. .tabs-item, .toolbar-item{
  293. cursor: pointer;
  294. padding: 0 10px;
  295. font-size: 12px;
  296. }
  297. .tabs-item.active{
  298. color: #0060df;
  299. border-top: 2px solid #0060df !important;
  300. background: #e9e9e9;
  301. }
  302.  
  303. .tabs-item:hover, .toolbar-item:hover{
  304. background: #e9e9e9;
  305. }
  306. /** 工具栏样式 END **/
  307. /** JSON 格式化样式 START **/
  308. ul.json-object,
  309. ul.json-array {
  310. list-style-type: none;
  311. margin: 0 0 0 2px;
  312. border-left: 1px dotted #5D6D7E;
  313. padding-left: 24px;
  314. }
  315. .json-brackets {
  316. font-weight: 700;
  317. }
  318. .json-key {
  319. /* color: #A31515;*/
  320. color: #910F93;
  321. cursor: pointer;
  322. }
  323. .json-string, .json-string a{
  324. /* color: #0b7500;*/
  325. color: #4B8A4C;
  326. }
  327. .json-number{
  328. /* color: #164FF0;*/
  329. color: #1a01cc;
  330. font-weight: 600;
  331. }
  332. .json-boolean{
  333. color: #905;
  334. font-weight: 600;
  335. }
  336. .json-null {
  337. /* color: #F1592A;*/
  338. color: #0031BC;
  339. font-weight: 600;
  340. }
  341. a.json-toggle {
  342. position: rElative;
  343. color: inherit;
  344. opacity: 0.2;
  345. text-decoration: none;
  346. }
  347. a.json-toggle:hover {
  348. opacity: 0.35;
  349. }
  350. a.json-toggle:active {
  351. opacity: 0.5;
  352. }
  353. a.json-toggle:focus {
  354. outline: none;
  355. }
  356. a.json-toggle:before {
  357. top: 2.5px;
  358. left: -15px;
  359. position: absolute;
  360. content: "";
  361. display: block;
  362. width: 0;
  363. height: 0;
  364. border-style: solid;
  365. border-width: 5px 0 5px 8px;
  366. border-color: transparent transparent transparent currentColor;
  367. transform: rotate(90deg);
  368. }
  369. a.json-toggle.collapsed:before {
  370. transform: rotate(0deg);
  371. }
  372. a.json-placeholder {
  373. color: #aaa;
  374. font-size: 12px;
  375. padding: 0 1em;
  376. text-decoration: none;
  377. }
  378. a.json-placeholder:hover {
  379. text-decoration: underline;
  380. }
  381. .json-curly-brackets {
  382. color: #6D9331;
  383. }
  384. .json-square-brackets{
  385. color: #8E9331;
  386. }
  387. /** 暗黑主题 START **/
  388. body.dark{
  389. background: #333333;
  390. }
  391. .dark li, .dark pre{
  392. color: #CCC;
  393. }
  394. .dark .json-toggle {
  395. opacity: 0.35;
  396. }
  397. .dark .json-toggle:hover {
  398. opacity: 0.5;
  399. }
  400. .dark .json-curly-brackets {
  401. color: #CE70D6;
  402. }
  403. .dark .json-square-brackets{
  404. color: #F1D700;
  405. }
  406. .dark .json-key {
  407. color: #7CDCFE;
  408. }
  409. .dark .json-string, .dark .json-string a{
  410. color: #CE9178;
  411. }
  412. .dark .json-number{
  413. color: #B5CEA8;
  414. }
  415. .dark .json-boolean{
  416. color: #569CD6;
  417. }
  418. .dark .json-null {
  419. color: #2D7AD6;
  420. }
  421. .dark jmnode {
  422. color: #7CDCFE !important;
  423. }
  424. /** 暗黑主题 END **/
  425. /** JSON 格式化样式 END **/
  426. /** 脑图样式 START **/
  427. #jmContainer{
  428. width: 100vw;
  429. height: calc(100vh - 57px);
  430. /* background:#F7F7F7 */
  431. }
  432. jmnode{
  433. display: flex;
  434. align-items: center;
  435. padding: 0 7px 0 22px;
  436. }
  437. jmnode{
  438. color: #475872 !important;
  439. box-shadow: none !important;
  440. background-color: transparent !important;
  441. }
  442. jmnode:hover{
  443. text-shadow: 1px 1px 1px currentColor;
  444. }
  445. jmnode.root {
  446. padding: 0;
  447. color: transparent !important;
  448. }
  449. jmnode:not(.root)::before, jmnode.root::before{
  450. content: " ";
  451. top: 50%;
  452. position: absolute;
  453. border-radius: 50%;
  454. transform: translateY(-50%);
  455. }
  456. jmnode:not(.root)::before{
  457. left: 0;
  458. width: 15px;
  459. height: 15px;
  460. background: rgba(${rgbaColor}, 0.5);
  461. }
  462. jmnode.root::before{
  463. left: 50%;
  464. width: 18px;
  465. height: 18px;
  466. transform: translate(-18px, -50%);
  467. background: rgba(${rgbaColor}, 0.7);
  468. }
  469. jmexpander{
  470. margin-top: 1px;
  471. line-height: 9px;
  472. background: #dfdfdf;
  473. }
  474. .mind-array{
  475. opacity: 0.5;
  476. font-size: 12px;
  477. padding-left: 5px;
  478. }
  479. /** 脑图样式 END **/
  480. /** 容器样式 START **/
  481. .tabs-container{
  482. overflow: auto;
  483. line-height: 1.5;
  484. font-family: monospace;
  485. }
  486. .tabs-container > div{
  487. display: none;
  488. }
  489. .tabs-container > div.active{
  490. display: block;
  491. }
  492. .tabs-container #formatContainer{
  493. padding: 10px;
  494. }
  495. .tabs-container #rawTextContainer{
  496. padding: 0 10px;
  497. }
  498. .tabs-container #rawTextContainer pre{
  499. display: block !important;
  500. overflow-wrap: break-word;
  501. white-space: pre-wrap;
  502. }
  503. /** 容器样式 END **/
  504. .layui-layer-tips{
  505. width: auto !important;
  506. }
  507. .tabs .theme{
  508. right: 60px;
  509. position: absolute;
  510. }
  511. `)
  512.  
  513. source.hide()
  514. // 将内容用eval函数处理下
  515. const jsonObject = eval(`(${rawText})`);
  516.  
  517. $("body").addClass('dark').append(`
  518. <div class="scroll-top"></div>
  519. <div class="flex-container">
  520. <div class="panel">
  521. <div class="tabs">
  522. <div class="tabs-item btn active" id="format">JSON格式化</div>
  523. <div class="tabs-item btn" id="viewJsonMind">JSON脑图</div>
  524. <div class="tabs-item btn" id="viewRawText">原始数据</div>
  525. <div class="theme">
  526. <label>主题: </label>
  527. <select>
  528. <option value="default">默认</option>
  529. <option value="dark">暗黑</option>
  530. </select>
  531. </div>
  532. </div>
  533. <div class="toolbar">
  534. <div class="toolbar-item btn" id="saveJson">保存</div>
  535. <div class="toolbar-item btn" id="copyJson">复制</div>
  536. <div class="toolbar-item btn" id="collapseAll">全部折叠</div>
  537. <div class="toolbar-item btn" id="expandAll">全部展开</div>
  538. <div class="toolbar-item btn" id="beautify" style="display: none">美化输出</div>
  539. <div class="searchbox">
  540. <input type="text" placeholder="过滤 JSON "/>
  541. <button class="clear" hidden></button>
  542. </div>
  543. </div>
  544. </div>
  545. <div class="tabs-container">
  546. <div class="active" id="formatContainer"></div>
  547. <div id="jmContainer"></div>
  548. <div id="rawTextContainer"><pre></pre></div>
  549. </div>
  550. </div>`)
  551.  
  552. let btnEvent = {
  553. isFormater: false,
  554. $rawText: $('#rawTextContainer'),
  555. /**
  556. * 保存为文件
  557. */
  558. download: {
  559. download: function(content, filename) {
  560. const link = document.createElement("a")
  561. link.href = content
  562. link.download = filename
  563. link.click()
  564. },
  565. saveJSON: function (text) {
  566. // 创建一个 Blob 对象,包含要下载的文本内容
  567. const blob = new Blob([text], { type: "text/plain;charset=utf-8" });
  568. const url = URL.createObjectURL(blob)
  569. let filename = new Date().getTime() + '.json';
  570. this.download(url, filename)
  571. URL.revokeObjectURL(url);
  572. },
  573. savePNG: () => jm.shoot(),
  574. },
  575. saveJson:function(){
  576. if($('#jmContainer').is(':visible')){
  577. this.download.savePNG()
  578. }else{
  579. this.download.saveJSON(this.$rawText.text())
  580. }
  581. },
  582. // 复制JSON文本内容
  583. copyJson: function(){
  584. GM_setClipboard(this.$rawText.text())
  585. layer.msg('复制成功', {time: 1500})
  586. },
  587. // 全部折叠
  588. collapseAll: function(){
  589. if($('#formatContainer').is(':visible')){
  590. try{
  591. $('.json-toggle').not('.collapsed').click()
  592. }catch(e){}
  593. }else{
  594. jm.collapse_all()
  595. }
  596. },
  597. // 全部展开
  598. expandAll: function(){
  599. if($('#formatContainer').is(':visible')){
  600. try{
  601. $('a.json-placeholder').click().remove()
  602. }catch(e){}
  603. }else{
  604. jm.expand_all()
  605. jm.scroll_node_to_center(jm.get_root())
  606. }
  607. },
  608. format: function(){},
  609. // 显示JSON脑图
  610. viewJsonMind: function(){},
  611. // 查看原始JSON内容
  612. viewRawText: function(){
  613. this.$rawText.html(source.clone())
  614. },
  615. // 美化
  616. beautify: function(){
  617. this.isFormater = !this.isFormater
  618. if(this.isFormater){
  619. this.$rawText.find('pre').text(JSON.stringify(jsonObject, null, 2))
  620. }else{
  621. this.viewRawText()
  622. }
  623. },
  624. init: function(){
  625. this.viewRawText()
  626.  
  627. // 按钮点击事件
  628. $('.btn').click(e => {
  629. const target = e.target, id = target.id
  630. if(target.classList.contains('tabs-item')){
  631. let index = $(target).index()
  632. $(target).addClass('active').siblings().removeClass("active")
  633. $('.tabs-container > div').removeClass("active").eq(index).addClass('active')
  634.  
  635. let beautifyEl = $('#beautify'),
  636. searchEl= $('.searchbox'),
  637. copyJsonEl= $('#copyJson'),
  638. aEl = $('#collapseAll, #expandAll')
  639. id === 'format' ? searchEl.show(): searchEl.hide()
  640. id === 'viewJsonMind' ? copyJsonEl.hide(): copyJsonEl.show()
  641. id === 'viewRawText' ? (beautifyEl.show() && aEl.hide()) : (beautifyEl.hide() && aEl.show())
  642. }
  643. this[id](target)
  644. })
  645.  
  646. return this
  647. }
  648. },
  649. jsonMind = {
  650. // JSON数据转换为jsMind所需要的数据结构
  651. convert: function(json){
  652. let children = []
  653. if(typeof json === 'object'){
  654. for(let key in json){
  655. let val = json[key],
  656. isArray = Array.isArray(val)
  657.  
  658. children.push({
  659. isArray,
  660. chain: key,
  661. id: key + '_' + Math.random(),
  662. topic: isArray ? `${key}<span class="mind-array">[${val.length}]</span>` : `${key}`,
  663. // children: this.convert(val)
  664. children: isArray ? this.convert(val[0]) : this.convert(val)
  665. })
  666. }
  667. }
  668. return children;
  669. },
  670. // 脑图节点调用链
  671. mindChain: function (node){
  672. let chain = node.data.chain
  673. if(!node.parent){
  674. return chain
  675. }
  676.  
  677. let parent = node.parent, parentChain = this.mindChain(parent)
  678. chain = parent.data.isArray ? `${parentChain}[0].${chain}` : `${parentChain}.${chain}`
  679. return chain
  680. },
  681. // 显示脑图
  682. show: function(json, isArr){
  683. jm.show({
  684. "meta":{
  685. "name":"JSON脑图",
  686. "author":"1220301855@qq.com",
  687. "version":"1.0"
  688. },
  689. "format":"node_tree",
  690. /* 数据内容 */
  691. "data": {
  692. "id": "root",
  693. "topic": 'Response',
  694. "direction": "left",
  695. "children": this.convert(json),
  696. "chain": isArr ? 'Response[0]' : 'Response'
  697. }
  698. })
  699.  
  700. setTimeout(() => jm.scroll_node_to_center(jm.get_root()), 300)
  701. return this
  702. },
  703. // 脑图节点事件
  704. event:function(){
  705. $("jmnode").on('dblclick mouseover mouseout', function(event){
  706. let that = $(this), node = jm.get_node(that.attr('nodeid'))
  707. if(!node.parent){
  708. return
  709. }
  710.  
  711. switch(event.type){
  712. case 'dblclick':
  713. GM_setClipboard(jsonMind.mindChain(node))
  714. layer.msg('节点路径复制成功', {time: 1500})
  715. break;
  716. case 'mouseover':
  717. let s = `<b>节点路径(双击复制)</b><br/>${jsonMind.mindChain(node)}`
  718. layer.tips(s, that, {
  719. time: 0,
  720. tips: [2, '#1e2732']
  721. });
  722. break;
  723. default:
  724. layer.closeAll()
  725. break;
  726. }
  727. })
  728. return this
  729. },
  730. collapseOrExpand: function(){
  731. $("jmnode").on('click', function(){
  732. let node = jm.get_node($(this).attr('nodeid'))
  733. jm.toggle_node(node)
  734. })
  735. return this
  736. },
  737. init: function(json){
  738. let isArr = Array.isArray(json);
  739. if(isArr){
  740. if(typeof json[0] !== 'object'){
  741. layer.msg('数据结构无法生成脑图', {time: 1000})
  742. return
  743. }
  744. json = json[0]
  745. }
  746.  
  747. if(!window.jm){
  748. window.jm = new jsMind({
  749. mode :'side',
  750. editable: false,
  751. container:'jmContainer',
  752. view: {
  753. hmargin: 50, // 思维导图距容器外框的最小水平距离
  754. vmargin: 50, // 思维导图距容器外框的最小垂直距离
  755. engine: 'svg', // 思维导图各节点之间线条的绘制引擎
  756. draggable: true, // 当容器不能完全容纳思维导图时,是否允许拖动画布代替鼠标滚动
  757. support_html : false,
  758. line_color: '#C4C9D0',
  759. },
  760. layout: {
  761. vspace: 7, // 节点之间的垂直间距
  762. hspace: 150, // 节点之间的水平空间
  763. },
  764. });
  765. }
  766.  
  767. this.show(json, isArr).event().collapseOrExpand()
  768. }
  769. },
  770. otherOperate = {
  771. // 过滤 JSON
  772. filterJSON: function(filter) {
  773. if(!filter){
  774. $('#formatContainer li').removeClass('hidden')
  775. return
  776. }
  777.  
  778. let chainSet= new Set()
  779. /**
  780. * 模糊匹配 JSON key
  781. * 假如`filter`值为`id`, querySelectorAll得到DOM节点
  782. * 得到:['.feedList.0.images.0.user_id', '.feedList.0.images.0', '.feedList.0.images', '.feedList.0', '.feedList']
  783. */
  784. document.querySelectorAll('#formatContainer *[json-path]').forEach(el => {
  785. let chain = $(el).attr('json-path')
  786. if(!chain){
  787. return
  788. }
  789. let newChain = chain.substr(chain.lastIndexOf('.'))
  790. if(!newChain.toLowerCase().includes(filter.toLowerCase())){
  791. return
  792. }
  793. chainSet.add(chain)
  794. while(chain = chain.substr(0, chain.lastIndexOf('.'))){
  795. chainSet.add(chain)
  796. }
  797. })
  798.  
  799. /**
  800. * 模糊匹配 JSON value
  801. */
  802. document.querySelectorAll("#formatContainer *[class*='json-']:not([class*='json-key']):not([class*='json-brackets'])")
  803. .forEach(el =>{
  804. let target = $(el),
  805. chain = target.parent().attr('json-path')
  806. if(!chain){
  807. return
  808. }
  809. let text = target.text()
  810. if(!text.toLowerCase().includes(filter.toLowerCase())){
  811. return
  812. }
  813. chainSet.add(chain)
  814. while(chain = chain.substr(0, chain.lastIndexOf('.'))){
  815. chainSet.add(chain)
  816. }
  817. })
  818.  
  819. $('#formatContainer li').addClass('hidden')
  820. chainSet.forEach(chain => {
  821. $(`#formatContainer *[json-path="${chain}"]`).removeClass('hidden')
  822. })
  823. },
  824. // JSON 过滤
  825. input: function(){
  826. let that = this
  827. $('input').on('input', function(){
  828. let val = $(this).val()
  829. val === '' ? $('.clear').attr('hidden', true) : $('.clear').attr('hidden', false)
  830. that.filterJSON(val)
  831. })
  832. return that
  833. },
  834. // 清空输入框内容
  835. clear: function(){
  836. let that = this
  837. $('.clear').click(function(){
  838. that.filterJSON()
  839. $('input').val('')
  840. $(this).attr('hidden', true)
  841. })
  842. return this
  843. },
  844. // 返回顶部
  845. scrollTop: function(){
  846. $('.scroll-top').click(function(){
  847. $('.tabs-container').animate({
  848. scrollTop: '0'
  849. }, 1000);
  850. })
  851. return this
  852. },
  853. // 所有a标签,看是否是图片,是图片生成预览图
  854. urlHover:function(){
  855. $("a.json-string[href]").hover(function(){
  856. var that = $(this),
  857. href = that.attr('href')
  858. if(Utils.isImg(href)){
  859. layer.tips(`<img src="${href}" />`, that, {
  860. time: 0,
  861. anim: 5,
  862. maxWidth: 500,
  863. tips: [3, '#d9d9d9']
  864. });
  865. }
  866. }, () => layer.closeAll())
  867. return this
  868. },
  869. // 提示key的JSONPath
  870. tipsJsonPath: function(){
  871. var that = this
  872. $(".json-key").hover(function(){
  873. let jsonPath = that.getJsonPath(this)
  874. layer.tips(jsonPath, this, {
  875. time: 0,
  876. anim: 5,
  877. maxWidth: 500,
  878. tips: [1, '#1e2732']
  879. })
  880. }, () => layer.closeAll())
  881. return this
  882. },
  883. // 单击key复制JSONPath
  884. copyJsonPath: function(){
  885. var that = this
  886. $(".json-key").click(function(){
  887. let jsonPath = that.getJsonPath(this)
  888. GM_setClipboard(jsonPath)
  889. layer.msg('复制成功', {time: 1500})
  890. })
  891. return this
  892. },
  893. getJsonPath: function(element){
  894. let jsonPath = $(element).parent().attr('json-path')
  895. return jsonPath.split('.').reduce((prev, next) => /^\d+$/.test(next) ? prev + `[${next}]` : prev + '.' + next)
  896. },
  897. init:function(){
  898. this.input().clear().scrollTop().urlHover().tipsJsonPath().copyJsonPath()
  899. }
  900. },
  901. theme = {
  902. // 切换主题
  903. changeTheme: function(){
  904. let that = this
  905. $('.theme select').change(function(e){
  906. let val = $(e.target).val()
  907. GM_setValue('theme', val)
  908. that.setTheme()
  909. })
  910. return this
  911. },
  912. // 设置主题
  913. setTheme: function(){
  914. let theme = GM_getValue('theme') || 'default'
  915. $('body').removeClass().addClass(theme)
  916. $('.theme select').val(theme)
  917. return this
  918. },
  919. init:function(){
  920. this.setTheme().changeTheme()
  921. }
  922. }
  923.  
  924. $('#formatContainer').jsonViewer(jsonObject, jsonpFun)
  925. theme.init()
  926. btnEvent.init()
  927. otherOperate.init()
  928. jsonMind.init(jsonObject)
  929. })(_jQuery)
  930. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址