json-worker.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456
  1. // 创建一个处理BigInt的JSON解析器
  2. const JSONBigInt = {
  3. // 自定义的parse方法,处理大数字
  4. parse: function(text) {
  5. // 先尝试预处理字符串,将可能的大整数标记出来
  6. // 以更精确的方式匹配JSON中的大整数
  7. const preparedText = this._markBigInts(text);
  8. try {
  9. // 使用标准JSON解析,同时使用reviver函数还原BigInt
  10. return JSON.parse(preparedText, this._reviver);
  11. } catch (e) {
  12. // 如果处理失败,尝试原始解析方式
  13. console.error('BigInt处理失败,回退到标准解析', e);
  14. return JSON.parse(text);
  15. }
  16. },
  17. // 将JSON字符串中的大整数标记为特殊格式
  18. _markBigInts: function(text) {
  19. // 这个正则匹配JSON中的数字,但需要避免匹配到引号内的字符串
  20. // 匹配模式: 找到数字前面是冒号或左方括号的情况(表示这是个值而不是键名)
  21. return text.replace(
  22. /([:,\[]\s*)(-?\d{16,})([,\]\}])/g,
  23. function(match, prefix, number, suffix) {
  24. // 将大数字转换为特殊格式的字符串
  25. return prefix + '"__BigInt__' + number + '"' + suffix;
  26. }
  27. );
  28. },
  29. // 恢复函数,将标记的BigInt字符串转回BigInt类型
  30. _reviver: function(key, value) {
  31. // 检查是否是我们标记的BigInt字符串
  32. if (typeof value === 'string' && value.startsWith('__BigInt__')) {
  33. // 提取数字部分
  34. const numStr = value.substring(10);
  35. try {
  36. // 尝试转换为BigInt
  37. return BigInt(numStr);
  38. } catch (e) {
  39. // 如果转换失败,保留原始字符串
  40. console.warn('无法转换为BigInt:', numStr);
  41. return numStr;
  42. }
  43. }
  44. return value;
  45. }
  46. };
  47. // 处理主线程消息
  48. self.onmessage = function(event) {
  49. // 格式化JSON
  50. if (event.data.jsonString) {
  51. // 发送格式化中的消息
  52. self.postMessage(['FORMATTING']);
  53. try {
  54. // 先预处理JSON字符串,防止大整数丢失精度
  55. let jsonObj;
  56. try {
  57. // 尝试使用自定义的BigInt解析器
  58. jsonObj = JSONBigInt.parse(event.data.jsonString);
  59. } catch (e) {
  60. // 如果解析失败,回退到标准解析
  61. console.error('BigInt解析失败,回退到标准解析', e);
  62. jsonObj = JSON.parse(event.data.jsonString);
  63. }
  64. // 如果是简单主题,直接返回格式化的JSON
  65. if (event.data.skin && event.data.skin === 'theme-simple') {
  66. // 处理BigInt特殊情况
  67. let formatted = JSON.stringify(jsonObj, function(key, value) {
  68. if (typeof value === 'bigint') {
  69. // 移除n后缀,只显示数字本身
  70. return value.toString();
  71. }
  72. // 处理普通数字,避免科学计数法
  73. if (typeof value === 'number' && value.toString().includes('e')) {
  74. // 大数字转为字符串以避免科学计数法
  75. return value.toLocaleString('fullwide', {useGrouping: false});
  76. }
  77. return value;
  78. }, 4);
  79. let html = '<div id="formattedJson"><pre class="rootItem">' +
  80. formatted.replace(/&/g, '&amp;')
  81. .replace(/</g, '&lt;')
  82. .replace(/>/g, '&gt;')
  83. .replace(/"/g, '&quot;')
  84. .replace(/'/g, '&#039;') +
  85. '</pre></div>';
  86. self.postMessage(['FORMATTED', html]);
  87. return;
  88. }
  89. // 默认主题 - 创建更丰富的HTML结构
  90. let html = '<div id="formattedJson">' +
  91. formatJsonToHtml(jsonObj) +
  92. '</div>';
  93. self.postMessage(['FORMATTED', html]);
  94. } catch (e) {
  95. // 处理错误情况
  96. self.postMessage(['FORMATTED', '<div id="formattedJson"><div class="error">格式化失败: ' + e.message + '</div></div>']);
  97. }
  98. }
  99. };
  100. // HTML特殊字符格式化
  101. function htmlspecialchars(str) {
  102. str = str.replace(/&/g, '&amp;');
  103. str = str.replace(/</g, '&lt;');
  104. str = str.replace(/>/g, '&gt;');
  105. str = str.replace(/"/g, '&quot;');
  106. str = str.replace(/'/g, '&#039;');
  107. return str;
  108. }
  109. // 格式化字符串值,如果是URL则转换为链接
  110. function formatStringValue(str) {
  111. // URL正则表达式,匹配 http/https/ftp 协议的URL
  112. const urlRegex = /^(https?:\/\/|ftp:\/\/)[^\s<>"'\\]+$/i;
  113. if (urlRegex.test(str)) {
  114. // 如果是URL,转换为链接
  115. const escapedUrl = htmlspecialchars(str);
  116. return '<a href="' + escapedUrl + '" target="_blank" rel="noopener noreferrer" data-is-link="1" data-link-url="' + escapedUrl + '">' + htmlspecialchars(str) + '</a>';
  117. } else {
  118. // 直接显示解析后的字符串内容,不需要重新转义
  119. // 这样可以保持用户原始输入的意图
  120. return htmlspecialchars(str);
  121. }
  122. }
  123. // 格式化JSON为HTML
  124. function formatJsonToHtml(json) {
  125. return createNode(json).getHTML();
  126. }
  127. // 创建节点
  128. function createNode(value) {
  129. let node = {
  130. type: getType(value),
  131. value: value,
  132. children: [],
  133. getHTML: function() {
  134. switch(this.type) {
  135. case 'string':
  136. // 判断原始字符串是否为URL
  137. if (isUrl(this.value)) {
  138. // 用JSON.stringify保证转义符显示,内容包裹在<a>里
  139. return '<div class="item item-line"><span class="string"><a href="'
  140. + htmlspecialchars(this.value) + '" target="_blank" rel="noopener noreferrer" data-is-link="1" data-link-url="' + htmlspecialchars(this.value) + '">'
  141. + htmlspecialchars(JSON.stringify(this.value)) + '</a></span></div>';
  142. } else {
  143. return '<div class="item item-line"><span class="string">' + formatStringValue(JSON.stringify(this.value)) + '</span></div>';
  144. }
  145. case 'number':
  146. // 确保大数字不使用科学计数法
  147. let numStr = typeof this.value === 'number' && this.value.toString().includes('e')
  148. ? this.value.toLocaleString('fullwide', {useGrouping: false})
  149. : this.value;
  150. return '<div class="item item-line"><span class="number">' +
  151. numStr +
  152. '</span></div>';
  153. case 'bigint':
  154. // 对BigInt类型特殊处理,只显示数字,不添加n后缀
  155. return '<div class="item item-line"><span class="number">' +
  156. this.value.toString() +
  157. '</span></div>';
  158. case 'boolean':
  159. return '<div class="item item-line"><span class="bool">' +
  160. this.value +
  161. '</span></div>';
  162. case 'null':
  163. return '<div class="item item-line"><span class="null">null</span></div>';
  164. case 'object':
  165. return this.getObjectHTML();
  166. case 'array':
  167. return this.getArrayHTML();
  168. default:
  169. return '';
  170. }
  171. },
  172. getObjectHTML: function() {
  173. if (!this.value || Object.keys(this.value).length === 0) {
  174. return '<div class="item item-object"><span class="brace">{</span><span class="brace">}</span></div>';
  175. }
  176. let html = '<div class="item item-object">' +
  177. '<span class="expand"></span>' +
  178. '<span class="brace">{</span>' +
  179. '<span class="ellipsis"></span>' +
  180. '<div class="kv-list">';
  181. let keys = Object.keys(this.value);
  182. keys.forEach((key, index) => {
  183. let prop = this.value[key];
  184. let childNode = createNode(prop);
  185. // 判断子节点是否为对象或数组,决定是否加item-block
  186. let itemClass = (childNode.type === 'object' || childNode.type === 'array') ? 'item item-block' : 'item';
  187. html += '<div class="' + itemClass + '">';
  188. // 如果值是对象或数组,在key前面添加展开按钮
  189. if (childNode.type === 'object' || childNode.type === 'array') {
  190. html += '<span class="expand"></span>';
  191. }
  192. html += '<span class="quote">"</span>' +
  193. '<span class="key">' + htmlspecialchars(key) + '</span>' +
  194. '<span class="quote">"</span>' +
  195. '<span class="colon">: </span>';
  196. // 添加值
  197. if (childNode.type === 'object' || childNode.type === 'array') {
  198. html += childNode.getInlineHTMLWithoutExpand();
  199. } else {
  200. html += childNode.getHTML().replace(/^<div class="item item-line">/, '').replace(/<\/div>$/, '');
  201. }
  202. // 如果不是最后一个属性,添加逗号
  203. if (index < keys.length - 1) {
  204. html += '<span class="comma">,</span>';
  205. }
  206. html += '</div>';
  207. });
  208. html += '</div><span class="brace">}</span></div>';
  209. return html;
  210. },
  211. getArrayHTML: function() {
  212. if (!this.value || this.value.length === 0) {
  213. return '<div class="item item-array"><span class="brace">[</span><span class="brace">]</span></div>';
  214. }
  215. let html = '<div class="item item-array">' +
  216. '<span class="expand"></span>' +
  217. '<span class="brace">[</span>' +
  218. '<span class="ellipsis"></span>' +
  219. '<div class="kv-list item-array-container">';
  220. this.value.forEach((item, index) => {
  221. let childNode = createNode(item);
  222. html += '<div class="item item-block item-array-element" data-array-index="' + index + '">';
  223. // 如果数组元素是对象或数组,在前面添加展开按钮
  224. if (childNode.type === 'object' || childNode.type === 'array') {
  225. html += '<span class="expand"></span>';
  226. html += childNode.getInlineHTMLWithoutExpand();
  227. } else {
  228. html += childNode.getHTML().replace(/^<div class="item item-line">/, '').replace(/<\/div>$/, '');
  229. }
  230. // 如果不是最后一个元素,添加逗号
  231. if (index < this.value.length - 1) {
  232. html += '<span class="comma">,</span>';
  233. }
  234. html += '</div>';
  235. });
  236. html += '</div><span class="brace">]</span></div>';
  237. return html;
  238. },
  239. // 新增内联HTML方法,用于在同一行显示开始大括号/方括号
  240. getInlineHTML: function() {
  241. switch(this.type) {
  242. case 'object':
  243. return this.getInlineObjectHTML();
  244. case 'array':
  245. return this.getInlineArrayHTML();
  246. default:
  247. return this.getHTML();
  248. }
  249. },
  250. // 新增不包含展开按钮的内联HTML方法
  251. getInlineHTMLWithoutExpand: function() {
  252. switch(this.type) {
  253. case 'object':
  254. return this.getInlineObjectHTMLWithoutExpand();
  255. case 'array':
  256. return this.getInlineArrayHTMLWithoutExpand();
  257. default:
  258. return this.getHTML();
  259. }
  260. },
  261. getInlineObjectHTML: function() {
  262. if (!this.value || Object.keys(this.value).length === 0) {
  263. return '<span class="brace">{</span><span class="brace">}</span>';
  264. }
  265. let html = '<span class="brace">{</span>' +
  266. '<span class="expand"></span>' +
  267. '<span class="ellipsis"></span>' +
  268. '<div class="kv-list">';
  269. let keys = Object.keys(this.value);
  270. keys.forEach((key, index) => {
  271. let prop = this.value[key];
  272. let childNode = createNode(prop);
  273. // 判断子节点是否为对象或数组,决定是否加item-block
  274. let itemClass = (childNode.type === 'object' || childNode.type === 'array') ? 'item item-block' : 'item';
  275. html += '<div class="' + itemClass + '">';
  276. if (childNode.type === 'object' || childNode.type === 'array') {
  277. html += '<span class="expand"></span>';
  278. }
  279. html += '<span class="quote">"</span>' +
  280. '<span class="key">' + htmlspecialchars(key) + '</span>' +
  281. '<span class="quote">"</span>' +
  282. '<span class="colon">: </span>';
  283. if (childNode.type === 'object' || childNode.type === 'array') {
  284. html += childNode.getInlineHTMLWithoutExpand();
  285. } else {
  286. html += childNode.getHTML().replace(/^<div class="item item-line">/, '').replace(/<\/div>$/, '');
  287. }
  288. if (index < keys.length - 1) {
  289. html += '<span class="comma">,</span>';
  290. }
  291. html += '</div>';
  292. });
  293. html += '</div><span class="brace">}</span>';
  294. return html;
  295. },
  296. getInlineArrayHTML: function() {
  297. if (!this.value || this.value.length === 0) {
  298. return '<span class="brace">[</span><span class="brace">]</span>';
  299. }
  300. let html = '<span class="brace">[</span>' +
  301. '<span class="expand"></span>' +
  302. '<span class="ellipsis"></span>' +
  303. '<div class="kv-list item-array-container">';
  304. this.value.forEach((item, index) => {
  305. let childNode = createNode(item);
  306. html += '<div class="item item-block item-array-element" data-array-index="' + index + '">';
  307. // 如果数组元素是对象或数组,在前面添加展开按钮
  308. if (childNode.type === 'object' || childNode.type === 'array') {
  309. html += '<span class="expand"></span>';
  310. html += childNode.getInlineHTMLWithoutExpand();
  311. } else {
  312. html += childNode.getHTML().replace(/^<div class="item item-line">/, '').replace(/<\/div>$/, '');
  313. }
  314. // 如果不是最后一个元素,添加逗号
  315. if (index < this.value.length - 1) {
  316. html += '<span class="comma">,</span>';
  317. }
  318. html += '</div>';
  319. });
  320. html += '</div><span class="brace">]</span>';
  321. return html;
  322. },
  323. getInlineObjectHTMLWithoutExpand: function() {
  324. if (!this.value || Object.keys(this.value).length === 0) {
  325. return '<span class="brace">{</span><span class="brace">}</span>';
  326. }
  327. let html = '<span class="brace">{</span>' +
  328. '<span class="ellipsis"></span>' +
  329. '<div class="kv-list">';
  330. let keys = Object.keys(this.value);
  331. keys.forEach((key, index) => {
  332. let prop = this.value[key];
  333. let childNode = createNode(prop);
  334. // 判断子节点是否为对象或数组,决定是否加item-block
  335. let itemClass = (childNode.type === 'object' || childNode.type === 'array') ? 'item item-block' : 'item';
  336. html += '<div class="' + itemClass + '">';
  337. if (childNode.type === 'object' || childNode.type === 'array') {
  338. html += '<span class="expand"></span>';
  339. }
  340. html += '<span class="quote">"</span>' +
  341. '<span class="key">' + htmlspecialchars(key) + '</span>' +
  342. '<span class="quote">"</span>' +
  343. '<span class="colon">: </span>';
  344. if (childNode.type === 'object' || childNode.type === 'array') {
  345. html += childNode.getInlineHTMLWithoutExpand();
  346. } else {
  347. html += childNode.getHTML().replace(/^<div class="item item-line">/, '').replace(/<\/div>$/, '');
  348. }
  349. if (index < keys.length - 1) {
  350. html += '<span class="comma">,</span>';
  351. }
  352. html += '</div>';
  353. });
  354. html += '</div><span class="brace">}</span>';
  355. return html;
  356. },
  357. getInlineArrayHTMLWithoutExpand: function() {
  358. if (!this.value || this.value.length === 0) {
  359. return '<span class="brace">[</span><span class="brace">]</span>';
  360. }
  361. let html = '<span class="brace">[</span>' +
  362. '<span class="ellipsis"></span>' +
  363. '<div class="kv-list item-array-container">';
  364. this.value.forEach((item, index) => {
  365. let childNode = createNode(item);
  366. html += '<div class="item item-block item-array-element" data-array-index="' + index + '">';
  367. // 确保所有类型的数组元素都能正确处理
  368. if (childNode.type === 'object' || childNode.type === 'array') {
  369. html += '<span class="expand"></span>';
  370. html += childNode.getInlineHTMLWithoutExpand();
  371. } else {
  372. html += childNode.getHTML().replace(/^<div class="item item-line">/, '').replace(/<\/div>$/, '');
  373. }
  374. // 如果不是最后一个元素,添加逗号
  375. if (index < this.value.length - 1) {
  376. html += '<span class="comma">,</span>';
  377. }
  378. html += '</div>';
  379. });
  380. html += '</div><span class="brace">]</span>';
  381. return html;
  382. }
  383. };
  384. return node;
  385. }
  386. // 获取值类型
  387. function getType(value) {
  388. if (value === null) return 'null';
  389. if (value === undefined) return 'undefined';
  390. let type = typeof value;
  391. // 特别处理BigInt类型
  392. if (type === 'bigint') return 'bigint';
  393. if (type === 'object') {
  394. if (Array.isArray(value)) return 'array';
  395. }
  396. return type;
  397. }
  398. function isUrl(str) {
  399. const urlRegex = /^(https?:\/\/|ftp:\/\/)[^\s<>"'\\]+$/i;
  400. return urlRegex.test(str);
  401. }