// ==UserScript== // @license MIT // @name JSON Viewer // @namespace http://tampermonkey.net/ // @version 0.4.8 // @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_getResourceText // @grant GM_setClipboard // @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-3.4.1.min.js // @require https://unpkg.com/layer-src@3.5.1/dist/layer.js // @require https://unpkg.com/jsmind@0.8.5/es6/jsmind.js // @resource swalStyle https://unpkg.com/jsmind@0.8.5/style/jsmind.css // @resource layerStyle https://unpkg.com/layer-src@3.5.1/dist/theme/default/layer.css // @downloadURL none // ==/UserScript== /*随机字符串*/ function randomString(e) { var e = e || 32, t = "ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678", a = t.length, n = ""; for (i = 0; i < e; i++){ n += t.charAt(Math.floor(Math.random() * a)); } return n } /*检查是否是图片链接*/ function isImg(pathImg) { var regexp = /^(http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?\/([\w#!:.?+=&%@!\-\/])*\.(gif|jpg|jpeg|png|GIF|JPG|PNG)([\w#!:.?+=&%@!\-\/])?/; return regexp.test(pathImg); } /** 检验内容是否是json格式的内容*/ function isJSON(str) { if (typeof str == 'string') { try { var obj = JSON.parse(str); if(typeof obj == 'object' && obj ){ console.log("is json") return true; }else{ console.log("is not json") return false; } } catch(e) { console.log("is not json", e) return false; } } } // jquery.json-viewer 插件 开始 // 解决和原网页jquery版本冲突 var jq = jQuery.noConflict(true); (function(jq){ /** * 检查 arg 是否为至少包含 1 个元素的数组或至少包含 1 个键的字典 */ function isCollapsable(arg) { return arg instanceof Object && Object.keys(arg).length > 0; } /** * 检查字符串是否为URL */ function isUrl(string) { var regexp = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/; return regexp.test(string); } /** * 将 JSON 对象转换为 HTML 表示形式 * @return string */ function json2html(json) { var html = ''; if (typeof json === 'string') { /* Escape tags */ json = json.replace(/&/g, '&').replace(//g, '>'); if (isUrl(json)){ html += `"${json}"`; } else{ html += `"${json}"`; } } else if (typeof json === 'number') { html += `${json}`; } else if (typeof json === 'boolean') { html += `${json}`; } else if (json === null) { html += 'null'; } else if (json instanceof Array) { if (json.length > 0) { html += '[
    '; for (var i = 0; i < json.length; ++i) { html += '
  1. '; /* Add toggle button if item is collapsable */ if (isCollapsable(json[i])) { html += ''; } html += json2html(json[i]); /* Add comma if item is not last */ if (i < json.length - 1) { html += ','; } html += '
  2. '; } html += '
]'; } else { html += '[]'; } } else if (typeof json === 'object') { var key_count = Object.keys(json).length; if (key_count > 0) { html += '{}'; } else { html += '{}'; } } return html; } jq.fn.jsonViewer = function(json, jsonpFunctionName) { return this.each(function() { /* Transform to HTML */ var html = json2html(json); /** is JSONP */ if(jsonpFunctionName !== undefined && jsonpFunctionName !== null){ html = `
${jsonpFunctionName}(
${html}
)
` } /* Insert HTML in target DOM element */ jq(this).html(html); /* Bind click on toggle buttons */ jq(this).off('click'); jq(this).on('click', 'a.json-toggle', function() { var target = jq(this).toggleClass('collapsed').siblings('ul.json-dict, ol.json-array'); target.toggle(); if (target.is(':visible')) { target.siblings('.json-placeholder').remove(); } else { var count = target.children('li').length; var placeholder = count + (count > 1 ? ' items' : ' item'); target.after('' + placeholder + ''); } return false; }); /* Simulate click on toggle button when placeholder is clicked */ jq(this).on('click', 'a.json-placeholder', function() { jq(this).siblings('a.json-toggle').click(); jq(this).siblings('a.json-placeholder').remove(); return false; }); }); }; })(jq); // jquery.json-viewer 插件 结束 (function() { 'use strict'; var source = jq('pre[style="word-wrap: break-word; white-space: pre-wrap;"]').first(); // 根据上面这一点没办法确定是需要添加json格式化工具,再加上对内容进行判断是不是json格式的内容 let rawText = source.html() if(!rawText){ return } // 判断是否为jsonp格式 let tokens = rawText.match(/^([^\s(]*)\s*\(([\s\S]*)\)\s*;?$/), jsonpFunctionName = null; if (tokens && tokens[1] && tokens[2]) { jsonpFunctionName = tokens[1] rawText= tokens[2] } // 如果是直接打开的json接口地址才需要格式化插件 if(source.length == 0 || !isJSON(rawText)){ return } // 随机rgb颜色 let rgbaColor = `${Math.random()*256}, ${Math.random()*256}, ${Math.random()*256}` // 添加样式 GM_addStyle(GM_getResourceText('swalStyle')) GM_addStyle(GM_getResourceText('layerStyle')) GM_addStyle(` #json-renderer { line-height: 1.5; font-size: 14px; display: block; font-family: monospace; margin: 15px 30px; } .btnGroup, .jmBtnGroup{ position: fixed; top: 30px; right: 30px; } .btn { border: 1px solid rgb(218, 220, 224); box-sizing: border-box; color: rgb(26, 115, 232); cursor: pointer; line-height: 28px; float: left; display: inherit; padding: 0 10px; } .btn:hover { background-color: rgb(210, 227, 252); } ul.json-dict, ol.json-array { list-style-type: none; margin: 0 0 0 2px; border-left: 1px dotted #5D6D7E; padding-left: 24px; } .b { font-weight: 700; } .jsonp{ margin-left: -30px; } .json-key { /* color: #A31515;*/ color: #910F93; } .json-string { /* color: #0b7500;*/ color: #4B8A4C; } .json-number { /* color: #164FF0;*/ color: #1a01cc; font-weight: 600; } .json-bool{ 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: 13px; padding: 0 1em; text-decoration: none; } a.json-placeholder:hover { text-decoration: underline; } #jsmind_container{ position: fixed; z-index: 999; top: 0; left: 0; display: none; width: 100vw; height: 100%; background:#F7F7F7 } .jmBtnGroup{ z-index: 9999; display: none; } /**脑图自定义样式*/ 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: 1px 1px 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; } .layui-layer-tips{ width: auto !important; } .mind-array{ opacity: 0.5; font-size: 12px; padding-left: 5px; } `) source.attr("id", "json-source").hide() // 将内容用eval函数处理下 var jsonObject = eval('(' + rawText + ')'); // 添加一个格式化显示的per元素 jq("body").append('
') .append(`
`) // JSON脑图相关 .append(`
`); // 调用格式化方法 jq('#json-renderer').jsonViewer(jsonObject, jsonpFunctionName); let btnEvent = { // 复制JSON文本内容 copyJson: function(){ GM_setClipboard(JSON.stringify(jsonObject)) layer.msg('复制成功', {time: 1500}) }, // 折叠全部的JSON结构 collapseJson: function(e){ var that = jq(e), v = that.val(); if(v === "折叠全部"){ jq('.json-toggle').not('.collapsed').click() }else{ jq('a.json-placeholder').click().remove(); } that.val(v === "折叠全部" ? "展开全部" : "折叠全部") }, // 查看原始/格式化JSON内容 switchRawText: function(e){ var that = jq(e), v = that.val(); that.val(v === '原文本' ? "格式化" : "原文本") jq('#json-source, #json-renderer').toggle(); }, // 显示JSON脑图 showMind: function(){ let isArr = false; if(Array.isArray(jsonObject)){ if(typeof jsonObject[0] !== 'object'){ layer.msg('数据结构无法生成脑图', {time: 1000}) return } isArr = true jsonObject = jsonObject[0] } jq('.jmBtnGroup').show() jq('#jsmind_container').fadeToggle(200); document.documentElement.style.overflow='hidden'; if(!window.jm){ window.jm = new jsMind({ mode :'side', editable: false, container:'jsmind_container', view: { hmargin: 50, // 思维导图距容器外框的最小水平距离 vmargin: 50, // 思维导图距容器外框的最小垂直距离 engine: 'svg', // 思维导图各节点之间线条的绘制引擎 draggable: true, // 当容器不能完全容纳思维导图时,是否允许拖动画布代替鼠标滚动 support_html : false, line_color: '#C4C9D0', }, layout: { vspace: 7, // 节点之间的垂直间距 hspace: 150, // 节点之间的水平空间 }, }); jm.show({ "meta":{ "name":"JSON脑图", "author":"1220301855@qq.com", "version":"1.0" }, "format":"node_tree", /* 数据内容 */ "data": { "id": "root", "topic": 'Response', "direction": "left", "children": convertToMind(jsonObject), "chain": isArr ? 'Response[i]' : 'Response' } }); // 脑图节点事件 jq("jmnode").on('dblclick mouseover mouseout', function(event){ let that = jq(this), node = jm.get_node(that.attr('nodeid')) if(!node.parent){ return } switch(event.type){ case 'dblclick': GM_setClipboard(mindChain(node)) layer.msg('节点路径复制成功', {time: 1500}) break; case 'mouseover': let s = `节点路径(双击复制)
${mindChain(node)}` layer.tips(s, that, { time: 0, tips: [2, '#1e2732'] }); break; default: layer.closeAll() break; } }) } }, // 收起节点 collapseNode: () => jm.collapse_all(), // 展开节点 expandNode: () => jm.expand_all(), // 关闭JSON脑图 closeMind: function(){ jq('.jmBtnGroup').hide() jq('#jsmind_container').fadeToggle(200); document.documentElement.style.overflow=''; }, } // 按钮点击事件 jq('.btn').click(e => btnEvent[e.target.id](e.target)) // 所有a标签,看是否是图片,是图片生成预览图 jq("a.json-string").hover(function(){ var that = jq(this), href = that.attr('href'); if(isImg(href)){ layer.tips(``, that, { time: 0, anim: 5, maxWidth: 500, tips: [2, '#d9d9d9'] }); } }, () => layer.closeAll()) })(); /** JSON数据转换为jsMind所需要的数据结构 */ function convertToMind(json){ let children = [] if(typeof json === 'object'){ for (let i = 0, keys = Object.keys(json); i < keys.length; i++){ let val = json[keys[i]]; if(val === null || ['string', 'number', 'boolean', 'undefined'].includes(typeof val)){ children.push({ id: keys[i] + '-' + randomString(10), topic: `${keys[i]}`, chain: keys[i] }) } else if(Array.isArray(val)){ children.push({ id: keys[i] + '-' + randomString(10), topic: `${keys[i]}[${val.length}]`, chain: keys[i], isArray: true, children: convertToMind(val[0], keys[i]) }) } else if(typeof val === 'object'){ children.push({ id: keys[i] + '-' + randomString(10), topic: `${keys[i]}`, chain: keys[i], children: convertToMind(val, keys[i]) }) } } } return children; } // 脑图节点调用链 function mindChain(node){ let s = node.data.chain if(!node.parent){ return s } let p = node.parent, r = mindChain(p) s = p.data.isArray ? `${r}[i].${s}` : `${r}.${s}` return s }