index.js 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866
  1. /**
  2. * FeHelper Json Format Tools
  3. */
  4. // 一些全局变量
  5. let editor = {};
  6. let LOCAL_KEY_OF_LAYOUT = 'local-layout-key';
  7. let JSON_LINT = 'jsonformat:json-lint-switch';
  8. let EDIT_ON_CLICK = 'jsonformat:edit-on-click';
  9. let AUTO_DECODE = 'jsonformat:auto-decode';
  10. new Vue({
  11. el: '#pageContainer',
  12. data: {
  13. defaultResultTpl: '<div class="x-placeholder"><img src="../json-format/json-demo.jpg" alt="json-placeholder"></div>',
  14. placeHolder: '',
  15. jsonFormattedSource: '',
  16. errorMsg: '',
  17. errorJsonCode: '',
  18. errorPos: '',
  19. jfCallbackName_start: '',
  20. jfCallbackName_end: '',
  21. jsonLintSwitch: true,
  22. autoDecode: false,
  23. fireChange: true,
  24. overrideJson: false,
  25. isInUSAFlag: false,
  26. nestedEscapeParse: false,
  27. // JSONPath查询相关
  28. jsonPathQuery: '',
  29. showJsonPathModal: false,
  30. showJsonPathExamplesModal: false,
  31. jsonPathResults: [],
  32. jsonPathError: '',
  33. copyButtonState: 'normal', // normal, copying, success, error
  34. jsonPathExamples: [
  35. { path: '$', description: '根对象' },
  36. { path: '$.data', description: '获取data属性' },
  37. { path: '$.data.*', description: '获取data下的所有属性' },
  38. { path: '$.data[0]', description: '获取data数组的第一个元素' },
  39. { path: '$.data[*]', description: '获取data数组的所有元素' },
  40. { path: '$.data[?(@.name)]', description: '获取data数组中有name属性的元素' },
  41. { path: '$..name', description: '递归查找所有name属性' },
  42. { path: '$.data[0:3]', description: '获取data数组的前3个元素' },
  43. { path: '$.data[-1]', description: '获取data数组的最后一个元素' },
  44. { path: '$.*.price', description: '获取所有子对象的price属性' }
  45. ]
  46. },
  47. mounted: function () {
  48. // 自动开关灯控制
  49. DarkModeMgr.turnLightAuto();
  50. this.placeHolder = this.defaultResultTpl;
  51. // 安全获取localStorage值(在沙盒环境中可能不可用)
  52. this.autoDecode = this.safeGetLocalStorage(AUTO_DECODE) === 'true';
  53. this.isInUSAFlag = this.isInUSA();
  54. this.jsonLintSwitch = (this.safeGetLocalStorage(JSON_LINT) !== 'false');
  55. this.overrideJson = (this.safeGetLocalStorage(EDIT_ON_CLICK) === 'true');
  56. // 兼容旧的localStorage键名,优先使用新的键名
  57. const oldAutoUnpack = this.safeGetLocalStorage('jsonformat:auto-unpack-json-string') === 'true';
  58. const oldEscape = this.safeGetLocalStorage('jsonformat:escape-json-string') === 'true';
  59. this.nestedEscapeParse = (this.safeGetLocalStorage('jsonformat:nested-escape-parse') === 'true') || oldAutoUnpack || oldEscape;
  60. this.changeLayout(this.safeGetLocalStorage(LOCAL_KEY_OF_LAYOUT));
  61. editor = CodeMirror.fromTextArea(this.$refs.jsonBox, {
  62. mode: "text/javascript",
  63. lineNumbers: true,
  64. matchBrackets: true,
  65. styleActiveLine: true,
  66. lineWrapping: true
  67. });
  68. //输入框聚焦
  69. editor.focus();
  70. // 格式化以后的JSON,点击以后可以重置原内容
  71. window._OnJsonItemClickByFH = (jsonTxt) => {
  72. if (this.overrideJson) {
  73. this.disableEditorChange(jsonTxt);
  74. }
  75. };
  76. editor.on('change', (editor, changes) => {
  77. this.jsonFormattedSource = editor.getValue().replace(/\n/gm, ' ');
  78. this.fireChange && this.format();
  79. });
  80. // 在tab创建或者更新时候,监听事件,看看是否有参数传递过来
  81. if (location.protocol === 'chrome-extension:') {
  82. chrome.tabs.query({currentWindow: true,active: true, }, (tabs) => {
  83. let activeTab = tabs.filter(tab => tab.active)[0];
  84. chrome.runtime.sendMessage({
  85. type: 'fh-dynamic-any-thing',
  86. thing: 'request-page-content',
  87. tabId: activeTab.id
  88. }).then(resp => {
  89. if(!resp || !resp.content) return ;
  90. editor.setValue(resp.content || '');
  91. this.format();
  92. });
  93. });
  94. }
  95. // 页面加载时自动获取并注入json-format页面的补丁
  96. this.loadPatchHotfix();
  97. },
  98. methods: {
  99. // 安全的JSON.stringify:
  100. // - 让 BigInt 在最终字符串中显示为未加引号的纯数字(用于显示与再解析)
  101. // - 普通 number 若为科学计数法,转为完整字符串(仍是数字)
  102. safeStringify(obj, space) {
  103. const tagged = JSON.stringify(obj, function(key, value) {
  104. if (typeof value === 'bigint') {
  105. // 用占位符标记,稍后去掉外层引号
  106. return `__FH_BIGINT__${value.toString()}`;
  107. }
  108. if (typeof value === 'number' && value.toString().includes('e')) {
  109. // 转成完整字符串,再在末尾转换为数字文本(通过占位)
  110. return `__FH_NUMSTR__${value.toLocaleString('fullwide', {useGrouping: false})}`;
  111. }
  112. return value;
  113. }, space);
  114. // 去掉占位符外层引号,恢复为裸数字文本
  115. return tagged
  116. .replace(/"__FH_BIGINT__(-?\d+)"/g, '$1')
  117. .replace(/"__FH_NUMSTR__(-?\d+)"/g, '$1');
  118. },
  119. // 安全获取localStorage值(在沙盒环境中可能不可用)
  120. safeGetLocalStorage(key) {
  121. try {
  122. return localStorage.getItem(key);
  123. } catch (e) {
  124. console.warn('localStorage不可用,使用默认值:', key);
  125. return null;
  126. }
  127. },
  128. // 安全设置localStorage值(在沙盒环境中可能不可用)
  129. safeSetLocalStorage(key, value) {
  130. try {
  131. localStorage.setItem(key, value);
  132. } catch (e) {
  133. console.warn('localStorage不可用,跳过保存:', key);
  134. }
  135. },
  136. loadPatchHotfix() {
  137. // 页面加载时自动获取并注入页面的补丁
  138. chrome.runtime.sendMessage({
  139. type: 'fh-dynamic-any-thing',
  140. thing: 'fh-get-tool-patch',
  141. toolName: 'json-format'
  142. }, patch => {
  143. if (patch) {
  144. if (patch.css) {
  145. const style = document.createElement('style');
  146. style.textContent = patch.css;
  147. document.head.appendChild(style);
  148. }
  149. if (patch.js && typeof patch.js === 'string' && patch.js.length < 50000) {
  150. try {
  151. new Function(patch.js)();
  152. } catch (e) {
  153. console.error('json-format补丁JS执行失败', e);
  154. }
  155. }
  156. }
  157. });
  158. },
  159. isInUSA: function () {
  160. // 通过时区判断是否在美国
  161. const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
  162. const isUSTimeZone = /^America\/(New_York|Chicago|Denver|Los_Angeles|Anchorage|Honolulu)/.test(timeZone);
  163. // 通过语言判断
  164. const language = navigator.language || navigator.userLanguage;
  165. const isUSLanguage = language.toLowerCase().indexOf('en-us') > -1;
  166. // 如果时区和语言都符合美国特征,则认为在美国
  167. return (isUSTimeZone && isUSLanguage);
  168. },
  169. format: function () {
  170. this.errorMsg = '';
  171. this.placeHolder = this.defaultResultTpl;
  172. this.jfCallbackName_start = '';
  173. this.jfCallbackName_end = '';
  174. let source = editor.getValue().replace(/\n/gm, ' ');
  175. if (!source) {
  176. return false;
  177. }
  178. // JSONP形式下的callback name
  179. let funcName = null;
  180. // json对象
  181. let jsonObj = null;
  182. // 下面校验给定字符串是否为一个合法的json(优先:宽松修正 + BigInt 安全解析)
  183. try {
  184. // 再看看是不是jsonp的格式
  185. let reg = /^([\w\.]+)\(\s*([\s\S]*)\s*\)$/igm;
  186. let matches = reg.exec(source);
  187. if (matches != null) {
  188. funcName = matches[1];
  189. source = matches[2];
  190. }
  191. jsonObj = parseWithBigInt(source);
  192. } catch (ex) {
  193. // 兜底:仅当 BigInt 安全解析失败时,才尝试 eval 系列
  194. try {
  195. jsonObj = new Function("return " + source)();
  196. } catch (exx) {
  197. try {
  198. jsonObj = new Function("return '" + source + "'")();
  199. if (typeof jsonObj === 'string') {
  200. try {
  201. jsonObj = parseWithBigInt(jsonObj);
  202. } catch (ie) {
  203. jsonObj = new Function("return " + jsonObj)();
  204. }
  205. }
  206. } catch (exxx) {
  207. this.errorMsg = exxx.message;
  208. }
  209. }
  210. }
  211. // 是json格式,可以进行JSON自动格式化
  212. if (jsonObj != null && typeof jsonObj === "object" && !this.errorMsg.length) {
  213. try {
  214. // 嵌套转义解析:深度解析字符串值中的JSON
  215. if (this.nestedEscapeParse && jsonObj != null && typeof jsonObj === 'object') {
  216. jsonObj = deepParseJSONStrings(jsonObj);
  217. }
  218. let sortType = document.querySelectorAll('[name=jsonsort]:checked')[0].value;
  219. if (sortType !== '0') {
  220. jsonObj = JsonABC.sortObj(jsonObj, parseInt(sortType), true);
  221. }
  222. // 关闭转义功能(因为已经深度解析为实际JSON了)
  223. if (typeof window.Formatter !== 'undefined' && window.Formatter.setEscapeEnabled) {
  224. window.Formatter.setEscapeEnabled(false);
  225. }
  226. source = this.safeStringify(jsonObj);
  227. } catch (ex) {
  228. // 通过JSON反解不出来的,一定有问题
  229. this.errorMsg = ex.message;
  230. }
  231. if (!this.errorMsg.length) {
  232. if (this.autoDecode) {
  233. (async () => {
  234. let txt = await JsonEnDecode.urlDecodeByFetch(source);
  235. source = JsonEnDecode.uniDecode(txt);
  236. await Formatter.format(source, null, this.escapeJsonString);
  237. })();
  238. } else {
  239. (async () => {
  240. await Formatter.format(source, null, this.escapeJsonString);
  241. })();
  242. }
  243. this.placeHolder = '';
  244. this.jsonFormattedSource = source;
  245. // 如果是JSONP格式的,需要把方法名也显示出来
  246. if (funcName != null) {
  247. this.jfCallbackName_start = funcName + '(';
  248. this.jfCallbackName_end = ')';
  249. } else {
  250. this.jfCallbackName_start = '';
  251. this.jfCallbackName_end = '';
  252. }
  253. this.$nextTick(() => {
  254. this.updateWrapperHeight();
  255. })
  256. }
  257. }
  258. if (this.errorMsg.length) {
  259. if (this.jsonLintSwitch) {
  260. return this.lintOn();
  261. } else {
  262. this.placeHolder = '<span class="x-error">' + this.errorMsg + '</span>';
  263. return false;
  264. }
  265. }
  266. return true;
  267. },
  268. compress: function () {
  269. if (this.format()) {
  270. let jsonTxt = this.jfCallbackName_start + this.jsonFormattedSource + this.jfCallbackName_end;
  271. this.disableEditorChange(jsonTxt);
  272. }
  273. },
  274. autoDecodeFn: function () {
  275. this.$nextTick(() => {
  276. this.safeSetLocalStorage(AUTO_DECODE, this.autoDecode);
  277. this.format();
  278. });
  279. },
  280. uniEncode: function () {
  281. editor.setValue(JsonEnDecode.uniEncode(editor.getValue()));
  282. },
  283. uniDecode: function () {
  284. editor.setValue(JsonEnDecode.uniDecode(editor.getValue()));
  285. },
  286. urlDecode: function () {
  287. JsonEnDecode.urlDecodeByFetch(editor.getValue()).then(text => editor.setValue(text));
  288. },
  289. updateWrapperHeight: function () {
  290. let curLayout = this.safeGetLocalStorage(LOCAL_KEY_OF_LAYOUT);
  291. let elPc = document.querySelector('#pageContainer');
  292. if (curLayout === 'up-down') {
  293. elPc.style.height = 'auto';
  294. } else {
  295. elPc.style.height = Math.max(elPc.scrollHeight, document.body.scrollHeight) + 'px';
  296. }
  297. },
  298. changeLayout: function (type) {
  299. let elPc = document.querySelector('#pageContainer');
  300. if (type === 'up-down') {
  301. elPc.classList.remove('layout-left-right');
  302. elPc.classList.add('layout-up-down');
  303. this.$refs.btnLeftRight.classList.remove('selected');
  304. this.$refs.btnUpDown.classList.add('selected');
  305. } else {
  306. elPc.classList.remove('layout-up-down');
  307. elPc.classList.add('layout-left-right');
  308. this.$refs.btnLeftRight.classList.add('selected');
  309. this.$refs.btnUpDown.classList.remove('selected');
  310. }
  311. this.safeSetLocalStorage(LOCAL_KEY_OF_LAYOUT, type);
  312. this.updateWrapperHeight();
  313. },
  314. setCache: function () {
  315. this.$nextTick(() => {
  316. this.safeSetLocalStorage(EDIT_ON_CLICK, this.overrideJson);
  317. });
  318. },
  319. lintOn: function () {
  320. this.$nextTick(() => {
  321. this.safeSetLocalStorage(JSON_LINT, this.jsonLintSwitch);
  322. });
  323. if (!editor.getValue().trim()) {
  324. return true;
  325. }
  326. this.$nextTick(() => {
  327. if (!this.jsonLintSwitch) {
  328. return;
  329. }
  330. let lintResult = JsonLint.lintDetect(editor.getValue());
  331. if (!isNaN(lintResult.line)) {
  332. this.placeHolder = '<div id="errorTips">' +
  333. '<div id="tipsBox">错误位置:' + (lintResult.line + 1) + '行,' + (lintResult.col + 1) + '列;缺少字符或字符不正确</div>' +
  334. '<div id="errorCode">' + lintResult.dom + '</div></div>';
  335. }
  336. });
  337. return false;
  338. },
  339. disableEditorChange: function (jsonTxt) {
  340. this.fireChange = false;
  341. this.$nextTick(() => {
  342. editor.setValue(jsonTxt);
  343. this.$nextTick(() => {
  344. this.fireChange = true;
  345. })
  346. })
  347. },
  348. openOptionsPage: function(event){
  349. event.preventDefault();
  350. event.stopPropagation();
  351. chrome.runtime.openOptionsPage();
  352. },
  353. openDonateModal: function(event){
  354. event.preventDefault();
  355. event.stopPropagation();
  356. chrome.runtime.sendMessage({
  357. type: 'fh-dynamic-any-thing',
  358. thing: 'open-donate-modal',
  359. params: { toolName: 'json-format' }
  360. });
  361. },
  362. setDemo: function () {
  363. let demo = '{"BigIntSupported":995815895020119788889,"date":"20180322","url":"https://www.baidu.com?wd=fehelper","img":"http://gips0.baidu.com/it/u=1490237218,4115737545&fm=3028&app=3028&f=JPEG&fmt=auto?w=1280&h=720","message":"Success !","status":200,"city":"北京","count":632,"data":{"shidu":"34%","pm25":73,"pm10":91,"quality":"良","wendu":"5","ganmao":"极少数敏感人群应减少户外活动","yesterday":{"date":"21日星期三","sunrise":"06:19","high":"高温 11.0℃","low":"低温 1.0℃","sunset":"18:26","aqi":85,"fx":"南风","fl":"<3级","type":"多云","notice":"阴晴之间,谨防紫外线侵扰"},"forecast":[{"date":"22日星期四","sunrise":"06:17","high":"高温 17.0℃","low":"低温 1.0℃","sunset":"18:27","aqi":98,"fx":"西南风","fl":"<3级","type":"晴","notice":"愿你拥有比阳光明媚的心情"},{"date":"23日星期五","sunrise":"06:16","high":"高温 18.0℃","low":"低温 5.0℃","sunset":"18:28","aqi":118,"fx":"无持续风向","fl":"<3级","type":"多云","notice":"阴晴之间,谨防紫外线侵扰"},{"date":"24日星期六","sunrise":"06:14","high":"高温 21.0℃","low":"低温 7.0℃","sunset":"18:29","aqi":52,"fx":"西南风","fl":"<3级","type":"晴","notice":"愿你拥有比阳光明媚的心情"},{"date":"25日星期日","sunrise":"06:13","high":"高温 22.0℃","low":"低温 7.0℃","sunset":"18:30","aqi":71,"fx":"西南风","fl":"<3级","type":"晴","notice":"愿你拥有比阳光明媚的心情"},{"date":"26日星期一","sunrise":"06:11","high":"高温 21.0℃","low":"低温 8.0℃","sunset":"18:31","aqi":97,"fx":"西南风","fl":"<3级","type":"多云","notice":"阴晴之间,谨防紫外线侵扰"}]}}';
  364. editor.setValue(demo);
  365. this.$nextTick(() => {
  366. this.format();
  367. })
  368. },
  369. nestedEscapeParseFn: function () {
  370. this.$nextTick(() => {
  371. this.safeSetLocalStorage('jsonformat:nested-escape-parse', this.nestedEscapeParse);
  372. this.format();
  373. });
  374. },
  375. // JSONPath查询功能
  376. executeJsonPath: function() {
  377. this.jsonPathError = '';
  378. this.jsonPathResults = [];
  379. if (!this.jsonPathQuery.trim()) {
  380. this.jsonPathError = '请输入JSONPath查询表达式';
  381. return;
  382. }
  383. let source = this.jsonFormattedSource || editor.getValue();
  384. if (!source.trim()) {
  385. this.jsonPathError = '请先输入JSON数据';
  386. return;
  387. }
  388. try {
  389. let jsonObj = JSON.parse(source);
  390. this.jsonPathResults = this.queryJsonPath(jsonObj, this.jsonPathQuery.trim());
  391. this.showJsonPathModal = true;
  392. } catch (error) {
  393. this.jsonPathError = 'JSON格式错误:' + error.message;
  394. this.showJsonPathModal = true;
  395. }
  396. },
  397. // JSONPath查询引擎
  398. queryJsonPath: function(obj, path) {
  399. let results = [];
  400. try {
  401. // 简化的JSONPath解析器
  402. if (path === '$') {
  403. results.push({ path: '$', value: obj });
  404. return results;
  405. }
  406. // 移除开头的$
  407. if (path.startsWith('$.')) {
  408. path = path.substring(2);
  409. } else if (path.startsWith('$')) {
  410. path = path.substring(1);
  411. }
  412. // 执行查询
  413. this.evaluateJsonPath(obj, path, '$', results);
  414. } catch (error) {
  415. throw new Error('JSONPath表达式错误:' + error.message);
  416. }
  417. return results;
  418. },
  419. // 递归评估JSONPath
  420. evaluateJsonPath: function(current, path, currentPath, results) {
  421. if (!path) {
  422. results.push({ path: currentPath, value: current });
  423. return;
  424. }
  425. // 处理递归搜索 ..
  426. if (path.startsWith('..')) {
  427. let remainPath = path.substring(2);
  428. this.recursiveSearch(current, remainPath, currentPath, results);
  429. return;
  430. }
  431. // 解析下一个路径片段
  432. let match;
  433. // 处理数组索引 [index] 或 [*] 或 [start:end]
  434. if ((match = path.match(/^\[([^\]]+)\](.*)$/))) {
  435. let indexExpr = match[1];
  436. let remainPath = match[2];
  437. if (!Array.isArray(current)) {
  438. return;
  439. }
  440. if (indexExpr === '*') {
  441. // 通配符:所有元素
  442. current.forEach((item, index) => {
  443. this.evaluateJsonPath(item, remainPath, currentPath + '[' + index + ']', results);
  444. });
  445. } else if (indexExpr.includes(':')) {
  446. // 数组切片 [start:end]
  447. let [start, end] = indexExpr.split(':').map(s => s.trim() === '' ? undefined : parseInt(s));
  448. let sliced = current.slice(start, end);
  449. sliced.forEach((item, index) => {
  450. let actualIndex = (start || 0) + index;
  451. this.evaluateJsonPath(item, remainPath, currentPath + '[' + actualIndex + ']', results);
  452. });
  453. } else if (indexExpr.startsWith('?(')) {
  454. // 过滤表达式 [?(@.prop)]
  455. current.forEach((item, index) => {
  456. if (this.evaluateFilter(item, indexExpr)) {
  457. this.evaluateJsonPath(item, remainPath, currentPath + '[' + index + ']', results);
  458. }
  459. });
  460. } else {
  461. // 具体索引
  462. let index = parseInt(indexExpr);
  463. if (index < 0) {
  464. index = current.length + index; // 负索引
  465. }
  466. if (index >= 0 && index < current.length) {
  467. this.evaluateJsonPath(current[index], remainPath, currentPath + '[' + index + ']', results);
  468. }
  469. }
  470. return;
  471. }
  472. // 处理属性访问 .property 或直接属性名
  473. if ((match = path.match(/^\.?([^.\[]+)(.*)$/))) {
  474. let prop = match[1];
  475. let remainPath = match[2];
  476. if (prop === '*') {
  477. // 通配符:所有属性
  478. if (typeof current === 'object' && current !== null) {
  479. Object.keys(current).forEach(key => {
  480. this.evaluateJsonPath(current[key], remainPath, currentPath + '.' + key, results);
  481. });
  482. }
  483. } else {
  484. // 具体属性
  485. if (typeof current === 'object' && current !== null && current.hasOwnProperty(prop)) {
  486. this.evaluateJsonPath(current[prop], remainPath, currentPath + '.' + prop, results);
  487. }
  488. }
  489. return;
  490. }
  491. // 处理方括号属性访问 ['property']
  492. if ((match = path.match(/^\['([^']+)'\](.*)$/))) {
  493. let prop = match[1];
  494. let remainPath = match[2];
  495. if (typeof current === 'object' && current !== null && current.hasOwnProperty(prop)) {
  496. this.evaluateJsonPath(current[prop], remainPath, currentPath + "['" + prop + "']", results);
  497. }
  498. return;
  499. }
  500. // 如果没有特殊符号,当作属性名处理
  501. if (typeof current === 'object' && current !== null && current.hasOwnProperty(path)) {
  502. results.push({ path: currentPath + '.' + path, value: current[path] });
  503. }
  504. },
  505. // 递归搜索
  506. recursiveSearch: function(current, targetProp, currentPath, results) {
  507. if (typeof current === 'object' && current !== null) {
  508. // 检查当前对象的属性
  509. if (current.hasOwnProperty(targetProp)) {
  510. results.push({ path: currentPath + '..' + targetProp, value: current[targetProp] });
  511. }
  512. // 递归搜索子对象
  513. Object.keys(current).forEach(key => {
  514. if (Array.isArray(current[key])) {
  515. current[key].forEach((item, index) => {
  516. this.recursiveSearch(item, targetProp, currentPath + '.' + key + '[' + index + ']', results);
  517. });
  518. } else if (typeof current[key] === 'object' && current[key] !== null) {
  519. this.recursiveSearch(current[key], targetProp, currentPath + '.' + key, results);
  520. }
  521. });
  522. }
  523. },
  524. // 简单的过滤器评估
  525. evaluateFilter: function(item, filterExpr) {
  526. // 简化的过滤器实现,只支持基本的属性存在性检查
  527. // 如 ?(@.name) 检查是否有name属性
  528. let match = filterExpr.match(/^\?\(@\.(\w+)\)$/);
  529. if (match) {
  530. let prop = match[1];
  531. return typeof item === 'object' && item !== null && item.hasOwnProperty(prop);
  532. }
  533. // 支持简单的比较 ?(@.age > 18)
  534. match = filterExpr.match(/^\?\(@\.(\w+)\s*([><=!]+)\s*(.+)\)$/);
  535. if (match) {
  536. let prop = match[1];
  537. let operator = match[2];
  538. let value = match[3];
  539. if (typeof item === 'object' && item !== null && item.hasOwnProperty(prop)) {
  540. let itemValue = item[prop];
  541. let compareValue = isNaN(value) ? value.replace(/['"]/g, '') : parseFloat(value);
  542. switch (operator) {
  543. case '>': return itemValue > compareValue;
  544. case '<': return itemValue < compareValue;
  545. case '>=': return itemValue >= compareValue;
  546. case '<=': return itemValue <= compareValue;
  547. case '==': return itemValue == compareValue;
  548. case '!=': return itemValue != compareValue;
  549. }
  550. }
  551. }
  552. return false;
  553. },
  554. // 显示JSONPath示例
  555. showJsonPathExamples: function() {
  556. this.showJsonPathExamplesModal = true;
  557. },
  558. // 使用JSONPath示例
  559. useJsonPathExample: function(path) {
  560. this.jsonPathQuery = path;
  561. this.closeJsonPathExamplesModal();
  562. },
  563. // 打开JSONPath查询模态框
  564. openJsonPathModal: function() {
  565. this.showJsonPathModal = true;
  566. // 清空之前的查询结果
  567. this.jsonPathResults = [];
  568. this.jsonPathError = '';
  569. this.copyButtonState = 'normal';
  570. },
  571. // 关闭JSONPath结果模态框
  572. closeJsonPathModal: function() {
  573. this.showJsonPathModal = false;
  574. this.copyButtonState = 'normal'; // 重置复制按钮状态
  575. },
  576. // 关闭JSONPath示例模态框
  577. closeJsonPathExamplesModal: function() {
  578. this.showJsonPathExamplesModal = false;
  579. },
  580. // 格式化JSONPath查询结果
  581. formatJsonPathResult: function(value) {
  582. if (typeof value === 'object') {
  583. return JSON.stringify(value, null, 2);
  584. }
  585. return String(value);
  586. },
  587. // 复制JSONPath查询结果
  588. copyJsonPathResults: function() {
  589. let resultText = this.jsonPathResults.map(result => {
  590. return `路径: ${result.path}\n值: ${this.formatJsonPathResult(result.value)}`;
  591. }).join('\n\n');
  592. // 设置复制状态
  593. this.copyButtonState = 'copying';
  594. navigator.clipboard.writeText(resultText).then(() => {
  595. this.copyButtonState = 'success';
  596. setTimeout(() => {
  597. this.copyButtonState = 'normal';
  598. }, 2000);
  599. }).catch(() => {
  600. // 兼容旧浏览器
  601. try {
  602. let textArea = document.createElement('textarea');
  603. textArea.value = resultText;
  604. document.body.appendChild(textArea);
  605. textArea.select();
  606. document.execCommand('copy');
  607. document.body.removeChild(textArea);
  608. this.copyButtonState = 'success';
  609. setTimeout(() => {
  610. this.copyButtonState = 'normal';
  611. }, 2000);
  612. } catch (error) {
  613. this.copyButtonState = 'error';
  614. setTimeout(() => {
  615. this.copyButtonState = 'normal';
  616. }, 2000);
  617. }
  618. });
  619. },
  620. // 下载JSONPath查询结果
  621. downloadJsonPathResults: function() {
  622. let resultText = this.jsonPathResults.map(result => {
  623. return `路径: ${result.path}\n值: ${this.formatJsonPathResult(result.value)}`;
  624. }).join('\n\n');
  625. // 基于JSONPath生成文件名
  626. let filename = this.generateFilenameFromPath(this.jsonPathQuery);
  627. let blob = new Blob([resultText], { type: 'text/plain;charset=utf-8' });
  628. let url = window.URL.createObjectURL(blob);
  629. let a = document.createElement('a');
  630. a.href = url;
  631. a.download = filename + '.txt';
  632. document.body.appendChild(a);
  633. a.click();
  634. document.body.removeChild(a);
  635. window.URL.revokeObjectURL(url);
  636. },
  637. // 根据JSONPath生成文件名
  638. generateFilenameFromPath: function(path) {
  639. if (!path || path === '$') {
  640. return 'jsonpath_root';
  641. }
  642. // 移除开头的$和.
  643. let cleanPath = path.replace(/^\$\.?/, '');
  644. // 替换特殊字符为下划线,保留数字、字母、点号、中划线
  645. let filename = cleanPath
  646. .replace(/[\[\]]/g, '_') // 方括号替换为下划线
  647. .replace(/[^\w\u4e00-\u9fa5.-]/g, '_') // 特殊字符替换为下划线,保留中文
  648. .replace(/_{2,}/g, '_') // 多个连续下划线合并为一个
  649. .replace(/^_|_$/g, ''); // 移除开头和结尾的下划线
  650. // 如果处理后为空,使用默认名称
  651. if (!filename) {
  652. return 'jsonpath_query';
  653. }
  654. // 限制文件名长度
  655. if (filename.length > 50) {
  656. filename = filename.substring(0, 50) + '_truncated';
  657. }
  658. return 'jsonpath_' + filename;
  659. },
  660. jumpToMockDataTool: function(event) {
  661. event.preventDefault();
  662. // 1. 先判断mock-data工具是否已安装
  663. // 方案:直接读取chrome.storage.local,判断DYNAMIC_TOOL:mock-data是否存在
  664. if (typeof chrome !== 'undefined' && chrome.storage && chrome.storage.local) {
  665. chrome.storage.local.get('DYNAMIC_TOOL:mock-data', result => {
  666. if (result && result['DYNAMIC_TOOL:mock-data']) {
  667. // 已安装,直接打开mock-data工具
  668. window.open('/mock-data/index.html', '_blank');
  669. } else {
  670. // 未安装,跳转到原href
  671. window.open('/options/index.html?query=数据Mock工具', '_blank');
  672. }
  673. });
  674. } else {
  675. // 兜底:如果无法访问chrome.storage,直接跳原href
  676. window.open('/options/index.html?query=数据Mock工具', '_blank');
  677. }
  678. }
  679. }
  680. });
  681. // 新增:递归解包嵌套JSON字符串的函数
  682. function deepParseJSONStrings(obj) {
  683. if (Array.isArray(obj)) {
  684. return obj.map(item => {
  685. // 对于数组中的字符串元素,也尝试解析为JSON
  686. if (typeof item === 'string' && item.trim()) {
  687. try {
  688. const parsed = JSON.parse(item);
  689. // 只递归对象或数组,且排除BigInt结构(如{s,e,c})和纯数字
  690. if (
  691. typeof parsed === 'object' &&
  692. parsed !== null &&
  693. (Array.isArray(parsed) || Object.prototype.toString.call(parsed) === '[object Object]') &&
  694. !(
  695. parsed &&
  696. typeof parsed.s === 'number' &&
  697. typeof parsed.e === 'number' &&
  698. Array.isArray(parsed.c) &&
  699. Object.keys(parsed).length === 3
  700. )
  701. ) {
  702. return deepParseJSONStrings(parsed);
  703. }
  704. } catch (e) {
  705. // 解析失败,保持原字符串
  706. }
  707. }
  708. return deepParseJSONStrings(item);
  709. });
  710. } else if (typeof obj === 'object' && obj !== null) {
  711. const newObj = {};
  712. for (const key in obj) {
  713. if (!obj.hasOwnProperty(key)) continue;
  714. const val = obj[key];
  715. if (typeof val === 'string' && val.trim()) {
  716. try {
  717. const parsed = JSON.parse(val);
  718. // 只递归对象或数组,且排除BigInt结构(如{s,e,c})和纯数字
  719. if (
  720. typeof parsed === 'object' &&
  721. parsed !== null &&
  722. (Array.isArray(parsed) || Object.prototype.toString.call(parsed) === '[object Object]') &&
  723. !(
  724. parsed &&
  725. typeof parsed.s === 'number' &&
  726. typeof parsed.e === 'number' &&
  727. Array.isArray(parsed.c) &&
  728. Object.keys(parsed).length === 3
  729. )
  730. ) {
  731. newObj[key] = deepParseJSONStrings(parsed);
  732. continue;
  733. }
  734. } catch (e) {
  735. // 解析失败,保持原值
  736. }
  737. }
  738. newObj[key] = deepParseJSONStrings(val);
  739. }
  740. return newObj;
  741. }
  742. return obj;
  743. }
  744. // 统一的 BigInt 安全解析(与format-lib/worker思路一致):
  745. // 1) 自动给未加引号的 key 补双引号;2) 为可能的超长数字加标记;3) 用 reviver 还原为 BigInt
  746. function parseWithBigInt(text) {
  747. // 先把使用单引号包裹的 key 统一替换成双引号
  748. let fixed = String(text).replace(/([\{,]\s*)'([^'\\]*?)'(\s*:)/g, '$1"$2"$3');
  749. // 补齐未加引号的 key
  750. const keyFixRegex = /([\{,]\s*)(\w+)(\s*:)/g;
  751. fixed = fixed.replace(keyFixRegex, '$1"$2"$3');
  752. // 标记 16 位及以上的整数(允许值后有空白,再跟 , ] } 或结尾)
  753. // 使用 offset 检查匹配位置是否在 JSON 字符串内部,避免破坏嵌套转义的 JSON 字符串
  754. fixed = fixed.replace(/([:,\[]\s*)(-?\d{16,})(\s*)(?=(?:,|\]|\}|$))/g, function(m, p1, num, sp, offset) {
  755. let inStr = false;
  756. let esc = false;
  757. for (let i = 0; i < offset; i++) {
  758. if (esc) { esc = false; continue; }
  759. if (fixed[i] === '\\') { esc = true; continue; }
  760. if (fixed[i] === '"') { inStr = !inStr; }
  761. }
  762. if (inStr) return m;
  763. return p1 + '"__BigInt__' + num + '"' + sp;
  764. });
  765. return JSON.parse(fixed, function(key, value) {
  766. if (typeof value === 'string' && value.indexOf('__BigInt__') === 0) {
  767. try { return BigInt(value.slice(10)); } catch(e) { return value.slice(10); }
  768. }
  769. return value;
  770. });
  771. }