Greasy Fork is available in English.

JSON formatter

Format JSON data in a beautiful way.

ของเมื่อวันที่ 24-07-2015 ดู เวอร์ชันล่าสุด

// ==UserScript==
// @name JSON formatter
// @namespace http://gerald.top
// @description Format JSON data in a beautiful way.
// @description:zh-CN 更加漂亮地显示JSON数据。
// @version 1.1.2
// @match *://*/*
// @grant GM_addStyle
// @grant GM_registerMenuCommand
// ==/UserScript==

function safeHTML(html) {
  return String(html).replace(/[<&"]/g, function (key) {
    return {
      '<': '&lt;',
      '&': '&amp;',
      '"': '&quote;',
    }[key];
  });
}

function join(list) {
  var html = [];
  var open = false;
  var last = null;
  var close = function () {
    html.push('</li>');
    open = false;
    last = null;
  };
  list.forEach(function (item) {
    if (open && !item.backwards)
      close();
    if (!open) {
      html.push('<li>');
      open = true;
    }
    if (item.backwards && last && last.forwards)
      html.push(last.separator);
    html.push(item.data);
    if (!item.forwards)
      close();
    else
      last = item;
  });
  if (open) html.push('</li>');
  return html.join('');
}

function getHtml(data, cls) {
  var html = '<span class="' + (cls || 'value ' + typeof data) + '" ' +
    'data-type="' + safeHTML(typeof data) + '" ' +
    'data-value="' + safeHTML(data) + '">' + safeHTML(data) + '</span>';
  return html;
}

function render(data) {
  if (Array.isArray(data)) {
    var arr = [];
    var ret = {
      backwards: true,
      forwards: true,
      separator: getHtml(',', 'separator'),
    };
    arr.push(getHtml('[', 'operator'));
    if (data.length) {
      arr.push('<ul>');
      arr.push(join(data.map(render)));
      arr.push('</ul>');
    } else {
      arr.push(getHtml('', 'separator'));
      ret.forwards = false;
    }
    arr.push(getHtml(']', 'operator'));
    ret.data = arr.join('');
    return ret;
  } else if (data === null)
    return {data: getHtml('null', 'value null'), backwards: true};
  else if (typeof data == 'object') {
    var arr = [];
    var ret = {
      backwards: true,
      forwards: true,
      separator: getHtml(',', 'separator'),
    };
    arr.push(getHtml('{', 'operator'));
    var objdata = [];
    for (var key in data) {
      objdata.push({
        data: getHtml(key, 'key'),
        forwards: true,
        separator: getHtml(':', 'separator'),
      });
      objdata.push(render(data[key]));
    }
    if (objdata.length) {
      arr.push('<ul>');
      arr.push(join(objdata));
      arr.push('</ul>');
    } else {
      arr.push(getHtml('', 'separator'));
      ret.backwards = ret.forwards = false;
    }
    arr.push(getHtml('}', 'operator'));
    ret.data = arr.join('');
    return ret;
  } else
    return {
      backwards: true,
      data: getHtml(data),
    };
}

function formatJSON() {
  if (config.formatted) {
    document.body.innerHTML = config.raw;
    config.formatted = false;
  } else {
    if (!('raw' in config)) {
      config.raw = document.body.innerHTML;
      config.data = JSON.parse(document.body.innerText);
      config.style = GM_addStyle(
        '*{font-family:Microsoft YaHei,Tahoma;font-size:15px;}' +
        'ul.root{padding-left:0;}' +
        'li{list-style:none;}' +
        '.separator{margin-right:.5em;}' +
        '.number{color:darkorange;}' +
        '.key{color:brown;}' +
        '.string{color:green;}' +
        '.operator{color:blue;}' +
        '.value{position:relative;cursor:pointer;}' +
        '.popup{position:absolute;top:0;left:0;right:0;bottom:0;}' +
        '.popup-data{position:absolute;top:0;left:0;width:100%;bottom:0;border:none;cursor:pointer;box-sizing:content-box;padding:2px;margin:-2px;outline:1px dotted gray;}' +
        '.popup-info{position:absolute;top:100%;margin-top:.5em;padding:.5em;border-radius:.5em;box-shadow:0 0 1em gray;background:white;z-index:1;white-space:nowrap;color:black;}' +
        '.info-key{font-weight:bold;}' +
        '.info-val{color:dodgerblue;}' +
        '.hide{display:none;}'
      );
      initPopup();
    }
    var ret = render(config.data);
    document.body.innerHTML = '<ul class="root"><li>' + ret.data + '</li></ul>';
    config.formatted = true;
    bindEvents(document.body.querySelector('.root'));
  }
}

function initPopup() {
  var popup = document.createElement('div');
  popup.className = 'popup';
  var input = document.createElement('input');
  input.className = 'popup-data';
  input.readOnly = true;
  popup.appendChild(input);
  var info = document.createElement('div');
  info.className = 'popup-info';
  popup.appendChild(info);
  var hide = function () {
    var parent = popup.parentNode;
    if (parent) parent.removeChild(popup);
  };
  input.addEventListener('mouseup', function (e) {
    e.preventDefault();
    this.select();
  }, false);
  popup.addEventListener('click', function (e) {
    e.stopPropagation();
  }, false);
  document.addEventListener('click', hide, false);
  config.popup = {
    node: popup,
    hide: hide,
    show: function (target) {
      target.appendChild(popup);
      input.value = target.dataset.value || '';
      input.select();
      input.focus();
      info.innerHTML = '<span class="info-key">type</span>: <span class="info-val">' + safeHTML(target.dataset.type) + '</span>';
    },
  };
}

function bindEvents(root) {
  root.addEventListener('click', function (e) {
    e.stopPropagation();
    var target = e.target;
    if (target.classList.contains('value'))
      config.popup.show(target);
    else
      config.popup.hide();
  }, false);
}

var config = {};
if (/\/json$/.test(document.contentType))
  formatJSON();
GM_registerMenuCommand('Toggle JSON format', formatJSON);