// ==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/[email protected]/dist/layer.js
// @require https://unpkg.com/[email protected]/es6/jsmind.js
// @resource swalStyle https://unpkg.com/[email protected]/style/jsmind.css
// @resource layerStyle https://unpkg.com/[email protected]/dist/theme/default/layer.css
// ==/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, '<').replace(/>/g, '>');
if (isUrl(json)){
html += `<a href="${json}" class="json-string">"${json}"</a>`;
}
else{
html += `<span class="json-string">"${json}"</span>`;
}
}
else if (typeof json === 'number') {
html += `<span class="json-number ">${json}</span>`;
}
else if (typeof json === 'boolean') {
html += `<span class="json-bool ">${json}</span>`;
}
else if (json === null) {
html += '<span class="json-null">null</span>';
}
else if (json instanceof Array) {
if (json.length > 0) {
html += '<span class="b">[</span><ol class="json-array">';
for (var i = 0; i < json.length; ++i) {
html += '<li>';
/* Add toggle button if item is collapsable */
if (isCollapsable(json[i])) {
html += '<a href class="json-toggle"></a>';
}
html += json2html(json[i]);
/* Add comma if item is not last */
if (i < json.length - 1) {
html += ',';
}
html += '</li>';
}
html += '</ol><span class="b">]</span>';
}
else {
html += '[]';
}
}
else if (typeof json === 'object') {
var key_count = Object.keys(json).length;
if (key_count > 0) {
html += '<span class="b">{</span><ul class="json-dict">';
for (var key in json) {
if (json.hasOwnProperty(key)) {
html += '<li>';
/* Add toggle button if item is collapsable */
if (isCollapsable(json[key])) {
html += '<a href class="json-toggle"></a>';
}
html += `<span class="json-key">"${key}"</span>: ${json2html(json[key])}`;
/* Add comma if item is not last */
if (--key_count > 0){
html += ',';
}
html += '</li>';
}
}
html += '</ul><span class="b">}</span>';
}
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 = `<div class="jsonp">${jsonpFunctionName}(</div>${html}<div class="jsonp">)</div>`
}
/* 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('<a href class="json-placeholder">' + placeholder + '</a>');
}
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('<div id="json-renderer"></div>')
.append(`<div class="btnGroup"><input class="btn" type="button" value="复制" id="copyJson"/>
<input class="btn" type="button" value="折叠全部" id="collapseJson"/>
<input class="btn" type="button" value="JSON脑图" id="showMind"/>
<input class="btn" type="button" value="原文本" id="switchRawText"/></div>`)
// JSON脑图相关
.append(`<div id="jsmind_container"></div>
<div class="jmBtnGroup"><input class="btn" type="button" value="收起节点" id="collapseNode"/>
<input class="btn" type="button" value="展开节点" id="expandNode"/>
<input class="btn" type="button" value="返回" id="closeMind"/></div>`);
// 调用格式化方法
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":"[email protected]",
"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 = `<b>节点路径(双击复制)</b><br/>${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(`<img src="${href}" />`, 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]}<span class="mind-array">[${val.length}]</span>`,
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
}