index.js 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742
  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. autoUnpackJsonString: 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. this.autoDecode = localStorage.getItem(AUTO_DECODE);
  52. this.autoDecode = this.autoDecode === 'true';
  53. this.isInUSAFlag = this.isInUSA();
  54. this.jsonLintSwitch = (localStorage.getItem(JSON_LINT) !== 'false');
  55. this.overrideJson = (localStorage.getItem(EDIT_ON_CLICK) === 'true');
  56. this.changeLayout(localStorage.getItem(LOCAL_KEY_OF_LAYOUT));
  57. editor = CodeMirror.fromTextArea(this.$refs.jsonBox, {
  58. mode: "text/javascript",
  59. lineNumbers: true,
  60. matchBrackets: true,
  61. styleActiveLine: true,
  62. lineWrapping: true
  63. });
  64. //输入框聚焦
  65. editor.focus();
  66. // 格式化以后的JSON,点击以后可以重置原内容
  67. window._OnJsonItemClickByFH = (jsonTxt) => {
  68. if (this.overrideJson) {
  69. this.disableEditorChange(jsonTxt);
  70. }
  71. };
  72. editor.on('change', (editor, changes) => {
  73. this.jsonFormattedSource = editor.getValue().replace(/\n/gm, ' ');
  74. this.fireChange && this.format();
  75. });
  76. // 在tab创建或者更新时候,监听事件,看看是否有参数传递过来
  77. if (location.protocol === 'chrome-extension:') {
  78. chrome.tabs.query({currentWindow: true,active: true, }, (tabs) => {
  79. let activeTab = tabs.filter(tab => tab.active)[0];
  80. chrome.runtime.sendMessage({
  81. type: 'fh-dynamic-any-thing',
  82. thing: 'request-page-content',
  83. tabId: activeTab.id
  84. }).then(resp => {
  85. if(!resp || !resp.content) return ;
  86. editor.setValue(resp.content || '');
  87. this.format();
  88. });
  89. });
  90. }
  91. // 页面加载时自动获取并注入json-format页面的补丁
  92. chrome.runtime.sendMessage({
  93. type: 'fh-dynamic-any-thing',
  94. thing: 'fh-get-tool-patch',
  95. toolName: 'json-format'
  96. }, patch => {
  97. if (patch) {
  98. if (patch.css) {
  99. const style = document.createElement('style');
  100. style.textContent = patch.css;
  101. document.head.appendChild(style);
  102. }
  103. if (patch.js) {
  104. try {
  105. if (window.evalCore && window.evalCore.getEvalInstance) {
  106. window.evalCore.getEvalInstance(window)(patch.js);
  107. }
  108. } catch (e) {
  109. console.error('json-format补丁JS执行失败', e);
  110. }
  111. }
  112. }
  113. });
  114. },
  115. methods: {
  116. isInUSA: function () {
  117. // 通过时区判断是否在美国
  118. const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
  119. const isUSTimeZone = /^America\/(New_York|Chicago|Denver|Los_Angeles|Anchorage|Honolulu)/.test(timeZone);
  120. // 通过语言判断
  121. const language = navigator.language || navigator.userLanguage;
  122. const isUSLanguage = language.toLowerCase().indexOf('en-us') > -1;
  123. // 如果时区和语言都符合美国特征,则认为在美国
  124. return (isUSTimeZone && isUSLanguage);
  125. },
  126. format: function () {
  127. this.errorMsg = '';
  128. this.placeHolder = this.defaultResultTpl;
  129. this.jfCallbackName_start = '';
  130. this.jfCallbackName_end = '';
  131. let source = editor.getValue().replace(/\n/gm, ' ');
  132. if (!source) {
  133. return false;
  134. }
  135. // JSONP形式下的callback name
  136. let funcName = null;
  137. // json对象
  138. let jsonObj = null;
  139. // 下面校验给定字符串是否为一个合法的json
  140. try {
  141. // 再看看是不是jsonp的格式
  142. let reg = /^([\w\.]+)\(\s*([\s\S]*)\s*\)$/igm;
  143. let matches = reg.exec(source);
  144. if (matches != null) {
  145. funcName = matches[1];
  146. source = matches[2];
  147. }
  148. // 这里可能会throw exception
  149. jsonObj = JSON.parse(source);
  150. } catch (ex) {
  151. // new Function的方式,能自动给key补全双引号,但是不支持bigint,所以是下下策,放在try-catch里搞
  152. try {
  153. jsonObj = new Function("return " + source)();
  154. } catch (exx) {
  155. try {
  156. // 再给你一次机会,是不是下面这种情况: "{\"ret\":\"0\", \"msg\":\"ok\"}"
  157. jsonObj = new Function("return '" + source + "'")();
  158. if (typeof jsonObj === 'string') {
  159. try {
  160. // 确保bigint不会失真
  161. jsonObj = JSON.parse(jsonObj);
  162. } catch (ie) {
  163. // 最后给你一次机会,是个字符串,老夫给你再转一次
  164. jsonObj = new Function("return " + jsonObj)();
  165. }
  166. }
  167. } catch (exxx) {
  168. this.errorMsg = exxx.message;
  169. }
  170. }
  171. }
  172. try{
  173. // 这里多做一个动作,给没有携带双引号的Key都自动加上,防止Long类型失真
  174. const regex = /([{,]\s*)(\w+)(\s*:)/g;
  175. source = source.replace(regex, '$1"$2"$3');
  176. jsonObj = JSON.parse(source);
  177. }catch(e){
  178. // 这里什么动作都不需要做,这种情况下转换失败的,肯定是Value被污染了,抛弃即可
  179. }
  180. // 新增:自动解包嵌套JSON字符串
  181. if (this.autoUnpackJsonString && jsonObj != null && typeof jsonObj === 'object') {
  182. jsonObj = deepParseJSONStrings(jsonObj);
  183. source = JSON.stringify(jsonObj);
  184. }
  185. // 是json格式,可以进行JSON自动格式化
  186. if (jsonObj != null && typeof jsonObj === "object" && !this.errorMsg.length) {
  187. try {
  188. let sortType = document.querySelectorAll('[name=jsonsort]:checked')[0].value;
  189. if (sortType !== '0') {
  190. jsonObj = JsonABC.sortObj(jsonObj, parseInt(sortType), true);
  191. }
  192. source = JSON.stringify(jsonObj);
  193. } catch (ex) {
  194. // 通过JSON反解不出来的,一定有问题
  195. this.errorMsg = ex.message;
  196. }
  197. if (!this.errorMsg.length) {
  198. if (this.autoDecode) {
  199. (async () => {
  200. let txt = await JsonEnDecode.urlDecodeByFetch(source);
  201. source = JsonEnDecode.uniDecode(txt);
  202. await Formatter.format(source);
  203. })();
  204. } else {
  205. (async () => {
  206. await Formatter.format(source);
  207. })();
  208. }
  209. this.placeHolder = '';
  210. this.jsonFormattedSource = source;
  211. // 如果是JSONP格式的,需要把方法名也显示出来
  212. if (funcName != null) {
  213. this.jfCallbackName_start = funcName + '(';
  214. this.jfCallbackName_end = ')';
  215. } else {
  216. this.jfCallbackName_start = '';
  217. this.jfCallbackName_end = '';
  218. }
  219. this.$nextTick(() => {
  220. this.updateWrapperHeight();
  221. })
  222. }
  223. }
  224. if (this.errorMsg.length) {
  225. if (this.jsonLintSwitch) {
  226. return this.lintOn();
  227. } else {
  228. this.placeHolder = '<span class="x-error">' + this.errorMsg + '</span>';
  229. return false;
  230. }
  231. }
  232. return true;
  233. },
  234. compress: function () {
  235. if (this.format()) {
  236. let jsonTxt = this.jfCallbackName_start + this.jsonFormattedSource + this.jfCallbackName_end;
  237. this.disableEditorChange(jsonTxt);
  238. }
  239. },
  240. autoDecodeFn: function () {
  241. this.$nextTick(() => {
  242. localStorage.setItem(AUTO_DECODE, this.autoDecode);
  243. this.format();
  244. });
  245. },
  246. uniEncode: function () {
  247. editor.setValue(JsonEnDecode.uniEncode(editor.getValue()));
  248. },
  249. uniDecode: function () {
  250. editor.setValue(JsonEnDecode.uniDecode(editor.getValue()));
  251. },
  252. urlDecode: function () {
  253. JsonEnDecode.urlDecodeByFetch(editor.getValue()).then(text => editor.setValue(text));
  254. },
  255. updateWrapperHeight: function () {
  256. let curLayout = localStorage.getItem(LOCAL_KEY_OF_LAYOUT);
  257. let elPc = document.querySelector('#pageContainer');
  258. if (curLayout === 'up-down') {
  259. elPc.style.height = 'auto';
  260. } else {
  261. elPc.style.height = Math.max(elPc.scrollHeight, document.body.scrollHeight) + 'px';
  262. }
  263. },
  264. changeLayout: function (type) {
  265. let elPc = document.querySelector('#pageContainer');
  266. if (type === 'up-down') {
  267. elPc.classList.remove('layout-left-right');
  268. elPc.classList.add('layout-up-down');
  269. this.$refs.btnLeftRight.classList.remove('selected');
  270. this.$refs.btnUpDown.classList.add('selected');
  271. } else {
  272. elPc.classList.remove('layout-up-down');
  273. elPc.classList.add('layout-left-right');
  274. this.$refs.btnLeftRight.classList.add('selected');
  275. this.$refs.btnUpDown.classList.remove('selected');
  276. }
  277. localStorage.setItem(LOCAL_KEY_OF_LAYOUT, type);
  278. this.updateWrapperHeight();
  279. },
  280. setCache: function () {
  281. this.$nextTick(() => {
  282. localStorage.setItem(EDIT_ON_CLICK, this.overrideJson);
  283. });
  284. },
  285. lintOn: function () {
  286. this.$nextTick(() => {
  287. localStorage.setItem(JSON_LINT, this.jsonLintSwitch);
  288. });
  289. if (!editor.getValue().trim()) {
  290. return true;
  291. }
  292. this.$nextTick(() => {
  293. if (!this.jsonLintSwitch) {
  294. return;
  295. }
  296. let lintResult = JsonLint.lintDetect(editor.getValue());
  297. if (!isNaN(lintResult.line)) {
  298. this.placeHolder = '<div id="errorTips">' +
  299. '<div id="tipsBox">错误位置:' + (lintResult.line + 1) + '行,' + (lintResult.col + 1) + '列;缺少字符或字符不正确</div>' +
  300. '<div id="errorCode">' + lintResult.dom + '</div></div>';
  301. }
  302. });
  303. return false;
  304. },
  305. disableEditorChange: function (jsonTxt) {
  306. this.fireChange = false;
  307. this.$nextTick(() => {
  308. editor.setValue(jsonTxt);
  309. this.$nextTick(() => {
  310. this.fireChange = true;
  311. })
  312. })
  313. },
  314. openOptionsPage: function(event){
  315. event.preventDefault();
  316. event.stopPropagation();
  317. chrome.runtime.openOptionsPage();
  318. },
  319. openDonateModal: function(event){
  320. event.preventDefault();
  321. event.stopPropagation();
  322. chrome.runtime.sendMessage({
  323. type: 'fh-dynamic-any-thing',
  324. thing: 'open-donate-modal',
  325. params: { toolName: 'json-format' }
  326. });
  327. },
  328. setDemo: function () {
  329. 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":"阴晴之间,谨防紫外线侵扰"}]}}';
  330. editor.setValue(demo);
  331. this.$nextTick(() => {
  332. this.format();
  333. })
  334. },
  335. autoUnpackJsonStringFn: function () {
  336. this.$nextTick(() => {
  337. localStorage.setItem('jsonformat:auto-unpack-json-string', this.autoUnpackJsonString);
  338. this.format();
  339. });
  340. },
  341. // JSONPath查询功能
  342. executeJsonPath: function() {
  343. this.jsonPathError = '';
  344. this.jsonPathResults = [];
  345. if (!this.jsonPathQuery.trim()) {
  346. this.jsonPathError = '请输入JSONPath查询表达式';
  347. return;
  348. }
  349. let source = this.jsonFormattedSource || editor.getValue();
  350. if (!source.trim()) {
  351. this.jsonPathError = '请先输入JSON数据';
  352. return;
  353. }
  354. try {
  355. let jsonObj = JSON.parse(source);
  356. this.jsonPathResults = this.queryJsonPath(jsonObj, this.jsonPathQuery.trim());
  357. this.showJsonPathModal = true;
  358. } catch (error) {
  359. this.jsonPathError = 'JSON格式错误:' + error.message;
  360. this.showJsonPathModal = true;
  361. }
  362. },
  363. // JSONPath查询引擎
  364. queryJsonPath: function(obj, path) {
  365. let results = [];
  366. try {
  367. // 简化的JSONPath解析器
  368. if (path === '$') {
  369. results.push({ path: '$', value: obj });
  370. return results;
  371. }
  372. // 移除开头的$
  373. if (path.startsWith('$.')) {
  374. path = path.substring(2);
  375. } else if (path.startsWith('$')) {
  376. path = path.substring(1);
  377. }
  378. // 执行查询
  379. this.evaluateJsonPath(obj, path, '$', results);
  380. } catch (error) {
  381. throw new Error('JSONPath表达式错误:' + error.message);
  382. }
  383. return results;
  384. },
  385. // 递归评估JSONPath
  386. evaluateJsonPath: function(current, path, currentPath, results) {
  387. if (!path) {
  388. results.push({ path: currentPath, value: current });
  389. return;
  390. }
  391. // 处理递归搜索 ..
  392. if (path.startsWith('..')) {
  393. let remainPath = path.substring(2);
  394. this.recursiveSearch(current, remainPath, currentPath, results);
  395. return;
  396. }
  397. // 解析下一个路径片段
  398. let match;
  399. // 处理数组索引 [index] 或 [*] 或 [start:end]
  400. if ((match = path.match(/^\[([^\]]+)\](.*)$/))) {
  401. let indexExpr = match[1];
  402. let remainPath = match[2];
  403. if (!Array.isArray(current)) {
  404. return;
  405. }
  406. if (indexExpr === '*') {
  407. // 通配符:所有元素
  408. current.forEach((item, index) => {
  409. this.evaluateJsonPath(item, remainPath, currentPath + '[' + index + ']', results);
  410. });
  411. } else if (indexExpr.includes(':')) {
  412. // 数组切片 [start:end]
  413. let [start, end] = indexExpr.split(':').map(s => s.trim() === '' ? undefined : parseInt(s));
  414. let sliced = current.slice(start, end);
  415. sliced.forEach((item, index) => {
  416. let actualIndex = (start || 0) + index;
  417. this.evaluateJsonPath(item, remainPath, currentPath + '[' + actualIndex + ']', results);
  418. });
  419. } else if (indexExpr.startsWith('?(')) {
  420. // 过滤表达式 [?(@.prop)]
  421. current.forEach((item, index) => {
  422. if (this.evaluateFilter(item, indexExpr)) {
  423. this.evaluateJsonPath(item, remainPath, currentPath + '[' + index + ']', results);
  424. }
  425. });
  426. } else {
  427. // 具体索引
  428. let index = parseInt(indexExpr);
  429. if (index < 0) {
  430. index = current.length + index; // 负索引
  431. }
  432. if (index >= 0 && index < current.length) {
  433. this.evaluateJsonPath(current[index], remainPath, currentPath + '[' + index + ']', results);
  434. }
  435. }
  436. return;
  437. }
  438. // 处理属性访问 .property 或直接属性名
  439. if ((match = path.match(/^\.?([^.\[]+)(.*)$/))) {
  440. let prop = match[1];
  441. let remainPath = match[2];
  442. if (prop === '*') {
  443. // 通配符:所有属性
  444. if (typeof current === 'object' && current !== null) {
  445. Object.keys(current).forEach(key => {
  446. this.evaluateJsonPath(current[key], remainPath, currentPath + '.' + key, results);
  447. });
  448. }
  449. } else {
  450. // 具体属性
  451. if (typeof current === 'object' && current !== null && current.hasOwnProperty(prop)) {
  452. this.evaluateJsonPath(current[prop], remainPath, currentPath + '.' + prop, results);
  453. }
  454. }
  455. return;
  456. }
  457. // 处理方括号属性访问 ['property']
  458. if ((match = path.match(/^\['([^']+)'\](.*)$/))) {
  459. let prop = match[1];
  460. let remainPath = match[2];
  461. if (typeof current === 'object' && current !== null && current.hasOwnProperty(prop)) {
  462. this.evaluateJsonPath(current[prop], remainPath, currentPath + "['" + prop + "']", results);
  463. }
  464. return;
  465. }
  466. // 如果没有特殊符号,当作属性名处理
  467. if (typeof current === 'object' && current !== null && current.hasOwnProperty(path)) {
  468. results.push({ path: currentPath + '.' + path, value: current[path] });
  469. }
  470. },
  471. // 递归搜索
  472. recursiveSearch: function(current, targetProp, currentPath, results) {
  473. if (typeof current === 'object' && current !== null) {
  474. // 检查当前对象的属性
  475. if (current.hasOwnProperty(targetProp)) {
  476. results.push({ path: currentPath + '..' + targetProp, value: current[targetProp] });
  477. }
  478. // 递归搜索子对象
  479. Object.keys(current).forEach(key => {
  480. if (Array.isArray(current[key])) {
  481. current[key].forEach((item, index) => {
  482. this.recursiveSearch(item, targetProp, currentPath + '.' + key + '[' + index + ']', results);
  483. });
  484. } else if (typeof current[key] === 'object' && current[key] !== null) {
  485. this.recursiveSearch(current[key], targetProp, currentPath + '.' + key, results);
  486. }
  487. });
  488. }
  489. },
  490. // 简单的过滤器评估
  491. evaluateFilter: function(item, filterExpr) {
  492. // 简化的过滤器实现,只支持基本的属性存在性检查
  493. // 如 ?(@.name) 检查是否有name属性
  494. let match = filterExpr.match(/^\?\(@\.(\w+)\)$/);
  495. if (match) {
  496. let prop = match[1];
  497. return typeof item === 'object' && item !== null && item.hasOwnProperty(prop);
  498. }
  499. // 支持简单的比较 ?(@.age > 18)
  500. match = filterExpr.match(/^\?\(@\.(\w+)\s*([><=!]+)\s*(.+)\)$/);
  501. if (match) {
  502. let prop = match[1];
  503. let operator = match[2];
  504. let value = match[3];
  505. if (typeof item === 'object' && item !== null && item.hasOwnProperty(prop)) {
  506. let itemValue = item[prop];
  507. let compareValue = isNaN(value) ? value.replace(/['"]/g, '') : parseFloat(value);
  508. switch (operator) {
  509. case '>': return itemValue > compareValue;
  510. case '<': return itemValue < compareValue;
  511. case '>=': return itemValue >= compareValue;
  512. case '<=': return itemValue <= compareValue;
  513. case '==': return itemValue == compareValue;
  514. case '!=': return itemValue != compareValue;
  515. }
  516. }
  517. }
  518. return false;
  519. },
  520. // 显示JSONPath示例
  521. showJsonPathExamples: function() {
  522. this.showJsonPathExamplesModal = true;
  523. },
  524. // 使用JSONPath示例
  525. useJsonPathExample: function(path) {
  526. this.jsonPathQuery = path;
  527. this.closeJsonPathExamplesModal();
  528. },
  529. // 打开JSONPath查询模态框
  530. openJsonPathModal: function() {
  531. this.showJsonPathModal = true;
  532. // 清空之前的查询结果
  533. this.jsonPathResults = [];
  534. this.jsonPathError = '';
  535. this.copyButtonState = 'normal';
  536. },
  537. // 关闭JSONPath结果模态框
  538. closeJsonPathModal: function() {
  539. this.showJsonPathModal = false;
  540. this.copyButtonState = 'normal'; // 重置复制按钮状态
  541. },
  542. // 关闭JSONPath示例模态框
  543. closeJsonPathExamplesModal: function() {
  544. this.showJsonPathExamplesModal = false;
  545. },
  546. // 格式化JSONPath查询结果
  547. formatJsonPathResult: function(value) {
  548. if (typeof value === 'object') {
  549. return JSON.stringify(value, null, 2);
  550. }
  551. return String(value);
  552. },
  553. // 复制JSONPath查询结果
  554. copyJsonPathResults: function() {
  555. let resultText = this.jsonPathResults.map(result => {
  556. return `路径: ${result.path}\n值: ${this.formatJsonPathResult(result.value)}`;
  557. }).join('\n\n');
  558. // 设置复制状态
  559. this.copyButtonState = 'copying';
  560. navigator.clipboard.writeText(resultText).then(() => {
  561. this.copyButtonState = 'success';
  562. setTimeout(() => {
  563. this.copyButtonState = 'normal';
  564. }, 2000);
  565. }).catch(() => {
  566. // 兼容旧浏览器
  567. try {
  568. let textArea = document.createElement('textarea');
  569. textArea.value = resultText;
  570. document.body.appendChild(textArea);
  571. textArea.select();
  572. document.execCommand('copy');
  573. document.body.removeChild(textArea);
  574. this.copyButtonState = 'success';
  575. setTimeout(() => {
  576. this.copyButtonState = 'normal';
  577. }, 2000);
  578. } catch (error) {
  579. this.copyButtonState = 'error';
  580. setTimeout(() => {
  581. this.copyButtonState = 'normal';
  582. }, 2000);
  583. }
  584. });
  585. },
  586. // 下载JSONPath查询结果
  587. downloadJsonPathResults: function() {
  588. let resultText = this.jsonPathResults.map(result => {
  589. return `路径: ${result.path}\n值: ${this.formatJsonPathResult(result.value)}`;
  590. }).join('\n\n');
  591. // 基于JSONPath生成文件名
  592. let filename = this.generateFilenameFromPath(this.jsonPathQuery);
  593. let blob = new Blob([resultText], { type: 'text/plain;charset=utf-8' });
  594. let url = window.URL.createObjectURL(blob);
  595. let a = document.createElement('a');
  596. a.href = url;
  597. a.download = filename + '.txt';
  598. document.body.appendChild(a);
  599. a.click();
  600. document.body.removeChild(a);
  601. window.URL.revokeObjectURL(url);
  602. },
  603. // 根据JSONPath生成文件名
  604. generateFilenameFromPath: function(path) {
  605. if (!path || path === '$') {
  606. return 'jsonpath_root';
  607. }
  608. // 移除开头的$和.
  609. let cleanPath = path.replace(/^\$\.?/, '');
  610. // 替换特殊字符为下划线,保留数字、字母、点号、中划线
  611. let filename = cleanPath
  612. .replace(/[\[\]]/g, '_') // 方括号替换为下划线
  613. .replace(/[^\w\u4e00-\u9fa5.-]/g, '_') // 特殊字符替换为下划线,保留中文
  614. .replace(/_{2,}/g, '_') // 多个连续下划线合并为一个
  615. .replace(/^_|_$/g, ''); // 移除开头和结尾的下划线
  616. // 如果处理后为空,使用默认名称
  617. if (!filename) {
  618. return 'jsonpath_query';
  619. }
  620. // 限制文件名长度
  621. if (filename.length > 50) {
  622. filename = filename.substring(0, 50) + '_truncated';
  623. }
  624. return 'jsonpath_' + filename;
  625. }
  626. }
  627. });
  628. // 新增:递归解包嵌套JSON字符串的函数
  629. function deepParseJSONStrings(obj) {
  630. if (Array.isArray(obj)) {
  631. return obj.map(deepParseJSONStrings);
  632. } else if (typeof obj === 'object' && obj !== null) {
  633. const newObj = {};
  634. for (const key in obj) {
  635. if (!obj.hasOwnProperty(key)) continue;
  636. const val = obj[key];
  637. if (typeof val === 'string') {
  638. try {
  639. const parsed = JSON.parse(val);
  640. // 只递归对象或数组
  641. if (typeof parsed === 'object' && parsed !== null) {
  642. newObj[key] = deepParseJSONStrings(parsed);
  643. continue;
  644. }
  645. } catch (e) {}
  646. }
  647. newObj[key] = deepParseJSONStrings(val);
  648. }
  649. return newObj;
  650. }
  651. return obj;
  652. }