JSON formatter

Format JSON data in a beautiful way.

当前为 2015-07-24 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name JSON formatter
  3. // @namespace http://gerald.top
  4. // @description Format JSON data in a beautiful way.
  5. // @description:zh-CN 更加漂亮地显示JSON数据。
  6. // @version 1.1.2
  7. // @match *://*/*
  8. // @grant GM_addStyle
  9. // @grant GM_registerMenuCommand
  10. // ==/UserScript==
  11.  
  12. function safeHTML(html) {
  13. return String(html).replace(/[<&"]/g, function (key) {
  14. return {
  15. '<': '&lt;',
  16. '&': '&amp;',
  17. '"': '&quote;',
  18. }[key];
  19. });
  20. }
  21.  
  22. function join(list) {
  23. var html = [];
  24. var open = false;
  25. var last = null;
  26. var close = function () {
  27. html.push('</li>');
  28. open = false;
  29. last = null;
  30. };
  31. list.forEach(function (item) {
  32. if (open && !item.backwards)
  33. close();
  34. if (!open) {
  35. html.push('<li>');
  36. open = true;
  37. }
  38. if (item.backwards && last && last.forwards)
  39. html.push(last.separator);
  40. html.push(item.data);
  41. if (!item.forwards)
  42. close();
  43. else
  44. last = item;
  45. });
  46. if (open) html.push('</li>');
  47. return html.join('');
  48. }
  49.  
  50. function getHtml(data) {
  51. var html = '<span class="' + (data.cls || 'value ' + typeof data.value) + '" ' +
  52. 'data-type="' + safeHTML(data.type || typeof data.value) + '" ' +
  53. 'data-value="' + safeHTML(data.value) + '">' + safeHTML(data.value) + '</span>';
  54. return html;
  55. }
  56.  
  57. function render(data) {
  58. if (Array.isArray(data)) {
  59. var arr = [];
  60. var ret = {
  61. backwards: true,
  62. forwards: true,
  63. separator: getHtml({value: ',', cls: 'separator'}),
  64. };
  65. arr.push(getHtml({value: '[', cls: 'operator'}));
  66. if (data.length) {
  67. arr.push('<ul>');
  68. arr.push(join(data.map(render)));
  69. arr.push('</ul>');
  70. } else {
  71. arr.push(getHtml({value: '', cls: 'separator'}));
  72. ret.forwards = false;
  73. }
  74. arr.push(getHtml({value: ']', cls: 'operator'}));
  75. ret.data = arr.join('');
  76. return ret;
  77. } else if (data === null)
  78. return {data: getHtml({value: data, cls: 'value null'}), backwards: true};
  79. else if (typeof data == 'object') {
  80. var arr = [];
  81. var ret = {
  82. backwards: true,
  83. forwards: true,
  84. separator: getHtml({value: ',', cls: 'separator'}),
  85. };
  86. arr.push(getHtml({value: '{', cls: 'operator'}));
  87. var objdata = [];
  88. for (var key in data) {
  89. objdata.push({
  90. data: getHtml({value: key, cls: 'key'}),
  91. forwards: true,
  92. separator: getHtml({value: ':', cls: 'separator'}),
  93. });
  94. objdata.push(render(data[key]));
  95. }
  96. if (objdata.length) {
  97. arr.push('<ul>');
  98. arr.push(join(objdata));
  99. arr.push('</ul>');
  100. } else {
  101. arr.push(getHtml({value: '', cls: 'separator'}));
  102. ret.backwards = ret.forwards = false;
  103. }
  104. arr.push(getHtml({value: '}', cls: 'operator'}));
  105. ret.data = arr.join('');
  106. return ret;
  107. } else
  108. return {
  109. backwards: true,
  110. data: getHtml({value: data}),
  111. };
  112. }
  113.  
  114. function formatJSON() {
  115. if (config.formatted) {
  116. document.body.innerHTML = config.raw;
  117. config.formatted = false;
  118. } else {
  119. if (!('raw' in config)) {
  120. config.raw = document.body.innerHTML;
  121. config.data = JSON.parse(document.body.innerText);
  122. config.style = GM_addStyle(
  123. '*{font-family:Microsoft YaHei,Tahoma;font-size:15px;}' +
  124. 'ul.root{padding-left:0;}' +
  125. 'li{list-style:none;}' +
  126. '.separator{margin-right:.5em;}' +
  127. '.number{color:darkorange;}' +
  128. '.null{color:gray;}' +
  129. '.key{color:brown;}' +
  130. '.string{color:green;}' +
  131. '.operator{color:blue;}' +
  132. '.value{position:relative;cursor:pointer;}' +
  133. '.popup{position:absolute;top:0;left:0;right:0;bottom:0;}' +
  134. '.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;}' +
  135. '.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;}' +
  136. '.info-key{font-weight:bold;}' +
  137. '.info-val{color:dodgerblue;}' +
  138. '.hide{display:none;}'
  139. );
  140. initPopup();
  141. }
  142. var ret = render(config.data);
  143. document.body.innerHTML = '<ul class="root"><li>' + ret.data + '</li></ul>';
  144. config.formatted = true;
  145. bindEvents(document.body.querySelector('.root'));
  146. }
  147. }
  148.  
  149. function initPopup() {
  150. var popup = document.createElement('div');
  151. popup.className = 'popup';
  152. var input = document.createElement('input');
  153. input.className = 'popup-data';
  154. input.readOnly = true;
  155. popup.appendChild(input);
  156. var info = document.createElement('div');
  157. info.className = 'popup-info';
  158. popup.appendChild(info);
  159. var hide = function () {
  160. var parent = popup.parentNode;
  161. if (parent) parent.removeChild(popup);
  162. };
  163. input.addEventListener('mouseup', function (e) {
  164. e.preventDefault();
  165. this.select();
  166. }, false);
  167. popup.addEventListener('click', function (e) {
  168. e.stopPropagation();
  169. }, false);
  170. document.addEventListener('click', hide, false);
  171. config.popup = {
  172. node: popup,
  173. hide: hide,
  174. show: function (target) {
  175. target.appendChild(popup);
  176. input.value = target.dataset.value || '';
  177. input.select();
  178. input.focus();
  179. info.innerHTML = '<span class="info-key">type</span>: <span class="info-val">' + safeHTML(target.dataset.type) + '</span>';
  180. },
  181. };
  182. }
  183.  
  184. function bindEvents(root) {
  185. root.addEventListener('click', function (e) {
  186. e.stopPropagation();
  187. var target = e.target;
  188. if (target.classList.contains('value'))
  189. config.popup.show(target);
  190. else
  191. config.popup.hide();
  192. }, false);
  193. }
  194.  
  195. var config = {};
  196. if (/\/json$/.test(document.contentType))
  197. formatJSON();
  198. GM_registerMenuCommand('Toggle JSON format', formatJSON);