/** * FeHelper Json Format Tools */ // 一些全局变量 let editor = {}; let LOCAL_KEY_OF_LAYOUT = 'local-layout-key'; let JSON_LINT = 'jsonformat:json-lint-switch'; let EDIT_ON_CLICK = 'jsonformat:edit-on-click'; let AUTO_DECODE = 'jsonformat:auto-decode'; new Vue({ el: '#pageContainer', data: { defaultResultTpl: '
json-placeholder
', placeHolder: '', jsonFormattedSource: '', errorMsg: '', errorJsonCode: '', errorPos: '', jfCallbackName_start: '', jfCallbackName_end: '', jsonLintSwitch: true, autoDecode: false, fireChange: true, overrideJson: false, isInUSAFlag: false, autoUnpackJsonString: false, // JSONPath查询相关 jsonPathQuery: '', showJsonPathModal: false, showJsonPathExamplesModal: false, jsonPathResults: [], jsonPathError: '', copyButtonState: 'normal', // normal, copying, success, error jsonPathExamples: [ { path: '$', description: '根对象' }, { path: '$.data', description: '获取data属性' }, { path: '$.data.*', description: '获取data下的所有属性' }, { path: '$.data[0]', description: '获取data数组的第一个元素' }, { path: '$.data[*]', description: '获取data数组的所有元素' }, { path: '$.data[?(@.name)]', description: '获取data数组中有name属性的元素' }, { path: '$..name', description: '递归查找所有name属性' }, { path: '$.data[0:3]', description: '获取data数组的前3个元素' }, { path: '$.data[-1]', description: '获取data数组的最后一个元素' }, { path: '$.*.price', description: '获取所有子对象的price属性' } ] }, mounted: function () { // 自动开关灯控制 DarkModeMgr.turnLightAuto(); this.placeHolder = this.defaultResultTpl; this.autoDecode = localStorage.getItem(AUTO_DECODE); this.autoDecode = this.autoDecode === 'true'; this.isInUSAFlag = this.isInUSA(); this.jsonLintSwitch = (localStorage.getItem(JSON_LINT) !== 'false'); this.overrideJson = (localStorage.getItem(EDIT_ON_CLICK) === 'true'); this.changeLayout(localStorage.getItem(LOCAL_KEY_OF_LAYOUT)); editor = CodeMirror.fromTextArea(this.$refs.jsonBox, { mode: "text/javascript", lineNumbers: true, matchBrackets: true, styleActiveLine: true, lineWrapping: true }); //输入框聚焦 editor.focus(); // 格式化以后的JSON,点击以后可以重置原内容 window._OnJsonItemClickByFH = (jsonTxt) => { if (this.overrideJson) { this.disableEditorChange(jsonTxt); } }; editor.on('change', (editor, changes) => { this.jsonFormattedSource = editor.getValue().replace(/\n/gm, ' '); this.fireChange && this.format(); }); // 在tab创建或者更新时候,监听事件,看看是否有参数传递过来 if (location.protocol === 'chrome-extension:') { chrome.tabs.query({currentWindow: true,active: true, }, (tabs) => { let activeTab = tabs.filter(tab => tab.active)[0]; chrome.runtime.sendMessage({ type: 'fh-dynamic-any-thing', thing: 'request-page-content', tabId: activeTab.id }).then(resp => { if(!resp || !resp.content) return ; editor.setValue(resp.content || ''); this.format(); }); }); } // 页面加载时自动获取并注入json-format页面的补丁 chrome.runtime.sendMessage({ type: 'fh-dynamic-any-thing', thing: 'fh-get-tool-patch', toolName: 'json-format' }, patch => { if (patch) { if (patch.css) { const style = document.createElement('style'); style.textContent = patch.css; document.head.appendChild(style); } if (patch.js) { try { if (window.evalCore && window.evalCore.getEvalInstance) { window.evalCore.getEvalInstance(window)(patch.js); } } catch (e) { console.error('json-format补丁JS执行失败', e); } } } }); }, methods: { isInUSA: function () { // 通过时区判断是否在美国 const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; const isUSTimeZone = /^America\/(New_York|Chicago|Denver|Los_Angeles|Anchorage|Honolulu)/.test(timeZone); // 通过语言判断 const language = navigator.language || navigator.userLanguage; const isUSLanguage = language.toLowerCase().indexOf('en-us') > -1; // 如果时区和语言都符合美国特征,则认为在美国 return (isUSTimeZone && isUSLanguage); }, format: function () { this.errorMsg = ''; this.placeHolder = this.defaultResultTpl; this.jfCallbackName_start = ''; this.jfCallbackName_end = ''; let source = editor.getValue().replace(/\n/gm, ' '); if (!source) { return false; } // JSONP形式下的callback name let funcName = null; // json对象 let jsonObj = null; // 下面校验给定字符串是否为一个合法的json try { // 再看看是不是jsonp的格式 let reg = /^([\w\.]+)\(\s*([\s\S]*)\s*\)$/igm; let matches = reg.exec(source); if (matches != null) { funcName = matches[1]; source = matches[2]; } // 这里可能会throw exception jsonObj = JSON.parse(source); } catch (ex) { // new Function的方式,能自动给key补全双引号,但是不支持bigint,所以是下下策,放在try-catch里搞 try { jsonObj = new Function("return " + source)(); } catch (exx) { try { // 再给你一次机会,是不是下面这种情况: "{\"ret\":\"0\", \"msg\":\"ok\"}" jsonObj = new Function("return '" + source + "'")(); if (typeof jsonObj === 'string') { try { // 确保bigint不会失真 jsonObj = JSON.parse(jsonObj); } catch (ie) { // 最后给你一次机会,是个字符串,老夫给你再转一次 jsonObj = new Function("return " + jsonObj)(); } } } catch (exxx) { this.errorMsg = exxx.message; } } } try{ // 这里多做一个动作,给没有携带双引号的Key都自动加上,防止Long类型失真 const regex = /([{,]\s*)(\w+)(\s*:)/g; source = source.replace(regex, '$1"$2"$3'); jsonObj = JSON.parse(source); }catch(e){ // 这里什么动作都不需要做,这种情况下转换失败的,肯定是Value被污染了,抛弃即可 } // 新增:自动解包嵌套JSON字符串 if (this.autoUnpackJsonString && jsonObj != null && typeof jsonObj === 'object') { jsonObj = deepParseJSONStrings(jsonObj); source = JSON.stringify(jsonObj); } // 是json格式,可以进行JSON自动格式化 if (jsonObj != null && typeof jsonObj === "object" && !this.errorMsg.length) { try { let sortType = document.querySelectorAll('[name=jsonsort]:checked')[0].value; if (sortType !== '0') { jsonObj = JsonABC.sortObj(jsonObj, parseInt(sortType), true); } source = JSON.stringify(jsonObj); } catch (ex) { // 通过JSON反解不出来的,一定有问题 this.errorMsg = ex.message; } if (!this.errorMsg.length) { if (this.autoDecode) { (async () => { let txt = await JsonEnDecode.urlDecodeByFetch(source); source = JsonEnDecode.uniDecode(txt); await Formatter.format(source); })(); } else { (async () => { await Formatter.format(source); })(); } this.placeHolder = ''; this.jsonFormattedSource = source; // 如果是JSONP格式的,需要把方法名也显示出来 if (funcName != null) { this.jfCallbackName_start = funcName + '('; this.jfCallbackName_end = ')'; } else { this.jfCallbackName_start = ''; this.jfCallbackName_end = ''; } this.$nextTick(() => { this.updateWrapperHeight(); }) } } if (this.errorMsg.length) { if (this.jsonLintSwitch) { return this.lintOn(); } else { this.placeHolder = '' + this.errorMsg + ''; return false; } } return true; }, compress: function () { if (this.format()) { let jsonTxt = this.jfCallbackName_start + this.jsonFormattedSource + this.jfCallbackName_end; this.disableEditorChange(jsonTxt); } }, autoDecodeFn: function () { this.$nextTick(() => { localStorage.setItem(AUTO_DECODE, this.autoDecode); this.format(); }); }, uniEncode: function () { editor.setValue(JsonEnDecode.uniEncode(editor.getValue())); }, uniDecode: function () { editor.setValue(JsonEnDecode.uniDecode(editor.getValue())); }, urlDecode: function () { JsonEnDecode.urlDecodeByFetch(editor.getValue()).then(text => editor.setValue(text)); }, updateWrapperHeight: function () { let curLayout = localStorage.getItem(LOCAL_KEY_OF_LAYOUT); let elPc = document.querySelector('#pageContainer'); if (curLayout === 'up-down') { elPc.style.height = 'auto'; } else { elPc.style.height = Math.max(elPc.scrollHeight, document.body.scrollHeight) + 'px'; } }, changeLayout: function (type) { let elPc = document.querySelector('#pageContainer'); if (type === 'up-down') { elPc.classList.remove('layout-left-right'); elPc.classList.add('layout-up-down'); this.$refs.btnLeftRight.classList.remove('selected'); this.$refs.btnUpDown.classList.add('selected'); } else { elPc.classList.remove('layout-up-down'); elPc.classList.add('layout-left-right'); this.$refs.btnLeftRight.classList.add('selected'); this.$refs.btnUpDown.classList.remove('selected'); } localStorage.setItem(LOCAL_KEY_OF_LAYOUT, type); this.updateWrapperHeight(); }, setCache: function () { this.$nextTick(() => { localStorage.setItem(EDIT_ON_CLICK, this.overrideJson); }); }, lintOn: function () { this.$nextTick(() => { localStorage.setItem(JSON_LINT, this.jsonLintSwitch); }); if (!editor.getValue().trim()) { return true; } this.$nextTick(() => { if (!this.jsonLintSwitch) { return; } let lintResult = JsonLint.lintDetect(editor.getValue()); if (!isNaN(lintResult.line)) { this.placeHolder = '
' + '
错误位置:' + (lintResult.line + 1) + '行,' + (lintResult.col + 1) + '列;缺少字符或字符不正确
' + '
' + lintResult.dom + '
'; } }); return false; }, disableEditorChange: function (jsonTxt) { this.fireChange = false; this.$nextTick(() => { editor.setValue(jsonTxt); this.$nextTick(() => { this.fireChange = true; }) }) }, openOptionsPage: function(event){ event.preventDefault(); event.stopPropagation(); chrome.runtime.openOptionsPage(); }, openDonateModal: function(event){ event.preventDefault(); event.stopPropagation(); chrome.runtime.sendMessage({ type: 'fh-dynamic-any-thing', thing: 'open-donate-modal', params: { toolName: 'json-format' } }); }, setDemo: function () { 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":"阴晴之间,谨防紫外线侵扰"}]}}'; editor.setValue(demo); this.$nextTick(() => { this.format(); }) }, autoUnpackJsonStringFn: function () { this.$nextTick(() => { localStorage.setItem('jsonformat:auto-unpack-json-string', this.autoUnpackJsonString); this.format(); }); }, // JSONPath查询功能 executeJsonPath: function() { this.jsonPathError = ''; this.jsonPathResults = []; if (!this.jsonPathQuery.trim()) { this.jsonPathError = '请输入JSONPath查询表达式'; return; } let source = this.jsonFormattedSource || editor.getValue(); if (!source.trim()) { this.jsonPathError = '请先输入JSON数据'; return; } try { let jsonObj = JSON.parse(source); this.jsonPathResults = this.queryJsonPath(jsonObj, this.jsonPathQuery.trim()); this.showJsonPathModal = true; } catch (error) { this.jsonPathError = 'JSON格式错误:' + error.message; this.showJsonPathModal = true; } }, // JSONPath查询引擎 queryJsonPath: function(obj, path) { let results = []; try { // 简化的JSONPath解析器 if (path === '$') { results.push({ path: '$', value: obj }); return results; } // 移除开头的$ if (path.startsWith('$.')) { path = path.substring(2); } else if (path.startsWith('$')) { path = path.substring(1); } // 执行查询 this.evaluateJsonPath(obj, path, '$', results); } catch (error) { throw new Error('JSONPath表达式错误:' + error.message); } return results; }, // 递归评估JSONPath evaluateJsonPath: function(current, path, currentPath, results) { if (!path) { results.push({ path: currentPath, value: current }); return; } // 处理递归搜索 .. if (path.startsWith('..')) { let remainPath = path.substring(2); this.recursiveSearch(current, remainPath, currentPath, results); return; } // 解析下一个路径片段 let match; // 处理数组索引 [index] 或 [*] 或 [start:end] if ((match = path.match(/^\[([^\]]+)\](.*)$/))) { let indexExpr = match[1]; let remainPath = match[2]; if (!Array.isArray(current)) { return; } if (indexExpr === '*') { // 通配符:所有元素 current.forEach((item, index) => { this.evaluateJsonPath(item, remainPath, currentPath + '[' + index + ']', results); }); } else if (indexExpr.includes(':')) { // 数组切片 [start:end] let [start, end] = indexExpr.split(':').map(s => s.trim() === '' ? undefined : parseInt(s)); let sliced = current.slice(start, end); sliced.forEach((item, index) => { let actualIndex = (start || 0) + index; this.evaluateJsonPath(item, remainPath, currentPath + '[' + actualIndex + ']', results); }); } else if (indexExpr.startsWith('?(')) { // 过滤表达式 [?(@.prop)] current.forEach((item, index) => { if (this.evaluateFilter(item, indexExpr)) { this.evaluateJsonPath(item, remainPath, currentPath + '[' + index + ']', results); } }); } else { // 具体索引 let index = parseInt(indexExpr); if (index < 0) { index = current.length + index; // 负索引 } if (index >= 0 && index < current.length) { this.evaluateJsonPath(current[index], remainPath, currentPath + '[' + index + ']', results); } } return; } // 处理属性访问 .property 或直接属性名 if ((match = path.match(/^\.?([^.\[]+)(.*)$/))) { let prop = match[1]; let remainPath = match[2]; if (prop === '*') { // 通配符:所有属性 if (typeof current === 'object' && current !== null) { Object.keys(current).forEach(key => { this.evaluateJsonPath(current[key], remainPath, currentPath + '.' + key, results); }); } } else { // 具体属性 if (typeof current === 'object' && current !== null && current.hasOwnProperty(prop)) { this.evaluateJsonPath(current[prop], remainPath, currentPath + '.' + prop, results); } } return; } // 处理方括号属性访问 ['property'] if ((match = path.match(/^\['([^']+)'\](.*)$/))) { let prop = match[1]; let remainPath = match[2]; if (typeof current === 'object' && current !== null && current.hasOwnProperty(prop)) { this.evaluateJsonPath(current[prop], remainPath, currentPath + "['" + prop + "']", results); } return; } // 如果没有特殊符号,当作属性名处理 if (typeof current === 'object' && current !== null && current.hasOwnProperty(path)) { results.push({ path: currentPath + '.' + path, value: current[path] }); } }, // 递归搜索 recursiveSearch: function(current, targetProp, currentPath, results) { if (typeof current === 'object' && current !== null) { // 检查当前对象的属性 if (current.hasOwnProperty(targetProp)) { results.push({ path: currentPath + '..' + targetProp, value: current[targetProp] }); } // 递归搜索子对象 Object.keys(current).forEach(key => { if (Array.isArray(current[key])) { current[key].forEach((item, index) => { this.recursiveSearch(item, targetProp, currentPath + '.' + key + '[' + index + ']', results); }); } else if (typeof current[key] === 'object' && current[key] !== null) { this.recursiveSearch(current[key], targetProp, currentPath + '.' + key, results); } }); } }, // 简单的过滤器评估 evaluateFilter: function(item, filterExpr) { // 简化的过滤器实现,只支持基本的属性存在性检查 // 如 ?(@.name) 检查是否有name属性 let match = filterExpr.match(/^\?\(@\.(\w+)\)$/); if (match) { let prop = match[1]; return typeof item === 'object' && item !== null && item.hasOwnProperty(prop); } // 支持简单的比较 ?(@.age > 18) match = filterExpr.match(/^\?\(@\.(\w+)\s*([><=!]+)\s*(.+)\)$/); if (match) { let prop = match[1]; let operator = match[2]; let value = match[3]; if (typeof item === 'object' && item !== null && item.hasOwnProperty(prop)) { let itemValue = item[prop]; let compareValue = isNaN(value) ? value.replace(/['"]/g, '') : parseFloat(value); switch (operator) { case '>': return itemValue > compareValue; case '<': return itemValue < compareValue; case '>=': return itemValue >= compareValue; case '<=': return itemValue <= compareValue; case '==': return itemValue == compareValue; case '!=': return itemValue != compareValue; } } } return false; }, // 显示JSONPath示例 showJsonPathExamples: function() { this.showJsonPathExamplesModal = true; }, // 使用JSONPath示例 useJsonPathExample: function(path) { this.jsonPathQuery = path; this.closeJsonPathExamplesModal(); }, // 打开JSONPath查询模态框 openJsonPathModal: function() { this.showJsonPathModal = true; // 清空之前的查询结果 this.jsonPathResults = []; this.jsonPathError = ''; this.copyButtonState = 'normal'; }, // 关闭JSONPath结果模态框 closeJsonPathModal: function() { this.showJsonPathModal = false; this.copyButtonState = 'normal'; // 重置复制按钮状态 }, // 关闭JSONPath示例模态框 closeJsonPathExamplesModal: function() { this.showJsonPathExamplesModal = false; }, // 格式化JSONPath查询结果 formatJsonPathResult: function(value) { if (typeof value === 'object') { return JSON.stringify(value, null, 2); } return String(value); }, // 复制JSONPath查询结果 copyJsonPathResults: function() { let resultText = this.jsonPathResults.map(result => { return `路径: ${result.path}\n值: ${this.formatJsonPathResult(result.value)}`; }).join('\n\n'); // 设置复制状态 this.copyButtonState = 'copying'; navigator.clipboard.writeText(resultText).then(() => { this.copyButtonState = 'success'; setTimeout(() => { this.copyButtonState = 'normal'; }, 2000); }).catch(() => { // 兼容旧浏览器 try { let textArea = document.createElement('textarea'); textArea.value = resultText; document.body.appendChild(textArea); textArea.select(); document.execCommand('copy'); document.body.removeChild(textArea); this.copyButtonState = 'success'; setTimeout(() => { this.copyButtonState = 'normal'; }, 2000); } catch (error) { this.copyButtonState = 'error'; setTimeout(() => { this.copyButtonState = 'normal'; }, 2000); } }); }, // 下载JSONPath查询结果 downloadJsonPathResults: function() { let resultText = this.jsonPathResults.map(result => { return `路径: ${result.path}\n值: ${this.formatJsonPathResult(result.value)}`; }).join('\n\n'); // 基于JSONPath生成文件名 let filename = this.generateFilenameFromPath(this.jsonPathQuery); let blob = new Blob([resultText], { type: 'text/plain;charset=utf-8' }); let url = window.URL.createObjectURL(blob); let a = document.createElement('a'); a.href = url; a.download = filename + '.txt'; document.body.appendChild(a); a.click(); document.body.removeChild(a); window.URL.revokeObjectURL(url); }, // 根据JSONPath生成文件名 generateFilenameFromPath: function(path) { if (!path || path === '$') { return 'jsonpath_root'; } // 移除开头的$和. let cleanPath = path.replace(/^\$\.?/, ''); // 替换特殊字符为下划线,保留数字、字母、点号、中划线 let filename = cleanPath .replace(/[\[\]]/g, '_') // 方括号替换为下划线 .replace(/[^\w\u4e00-\u9fa5.-]/g, '_') // 特殊字符替换为下划线,保留中文 .replace(/_{2,}/g, '_') // 多个连续下划线合并为一个 .replace(/^_|_$/g, ''); // 移除开头和结尾的下划线 // 如果处理后为空,使用默认名称 if (!filename) { return 'jsonpath_query'; } // 限制文件名长度 if (filename.length > 50) { filename = filename.substring(0, 50) + '_truncated'; } return 'jsonpath_' + filename; } } }); // 新增:递归解包嵌套JSON字符串的函数 function deepParseJSONStrings(obj) { if (Array.isArray(obj)) { return obj.map(deepParseJSONStrings); } else if (typeof obj === 'object' && obj !== null) { const newObj = {}; for (const key in obj) { if (!obj.hasOwnProperty(key)) continue; const val = obj[key]; if (typeof val === 'string') { try { const parsed = JSON.parse(val); // 只递归对象或数组 if (typeof parsed === 'object' && parsed !== null) { newObj[key] = deepParseJSONStrings(parsed); continue; } } catch (e) {} } newObj[key] = deepParseJSONStrings(val); } return newObj; } return obj; }