【写在前面】在实习中,遇到了如下需求:
待格式化数据大概长这样,里面存在Json乱码以及由$$包裹的公式
目标格式:
一、Json格式化
我们这里的任务主要分为两部分:
- 解析一个可能包含嵌套的 JSON 字符串
- 格式化 JSON 对象
因为我们不知道后端返回的数据嵌套了多少层,所以均采用递归的方式,具体代码如下:
1、解析一个可能包含嵌套的 JSON 字符串
parseAndFormatNestedJson(jsonString) {
const recursiveParse = jsonStr => {
try {
let parsedData = JSON.parse(jsonStr);
if (typeof parsedData === 'string') {
return recursiveParse(parsedData);
}
for (let key in parsedData) {
if (typeof parsedData[key] === 'string') {
try {
parsedData[key] = recursiveParse(parsedData[key]);
} catch (e) {
// 保留原始字符串
}
}
}
return parsedData;
} catch (e) {
return jsonStr;
}
};
const parsedResult = recursiveParse(jsonString);
return this.formatJson(parsedResult).replace(/\$\$/g, '$').replace(/\\\\/g, '\\');
},
这个函数的目的是解析一个可能包含嵌套的 JSON 字符串,并调用
formatJson
函数来格式化解析后的对象。处理过程如下:
-
递归解析 (
recursiveParse
): 这是一个内部函数,使用递归来处理嵌套的 JSON 字符串。- 使用
JSON.parse
尝试解析输入的字符串jsonStr
。 - 如果解析后的结果是字符串类型,表示可能是嵌套的 JSON 字符串,因此再次调用
recursiveParse
来解析这个字符串。 - 如果结果是一个对象,遍历该对象的每个属性。如果属性值是字符串类型,尝试对这个字符串进行递归解析。
- 如果在解析过程中遇到异常(比如非法的 JSON 格式),会捕获异常并返回原始字符串。
- 使用
-
格式化和替换: 解析完成后的 JSON 对象通过
formatJson
进行格式化。格式化完成后,使用字符串替换来处理特殊字符,如将$$
替换为$(方便展示)
,将\\\\
替换为\\(处理异常)
2、格式化 JSON 对象
formatJson(obj, level = 0) {
const spaces = ' '.repeat(level);
if (typeof obj !== 'object' || obj === null) {
return JSON.stringify(obj);
}
if (Array.isArray(obj)) {
const items = obj.map(item => `\n${spaces} ${this.formatJson(item, level + 1)}`);
return `[${items.join(', ')}\n${spaces}]`;
} else {
const entries = Object.entries(obj).map(([key, value]) =>
`\n${spaces} "${key}": ${this.formatJson(value, level + 1)}`
);
return `{${entries.join(', ')}\n${spaces}}`;
}
}
这个函数用于格式化 JSON 对象,使其更易于阅读。它支持递归格式化对象和数组。
- 基础情况: 如果输入不是对象(包括数组),或者为
null
,直接使用JSON.stringify
来转换为字符串。 - 数组处理: 如果是数组,对每个元素递归调用
formatJson
并增加缩进,然后将结果拼接成完整的数组格式。 - 对象处理: 如果是对象,对每个键值对递归调用
formatJson
并增加缩进,然后将结果拼接成完整的对象格式。
二、渲染latex公式
这里涉及到 第三方库 Mathjax 的使用,Mathjax具有强大的功能,配置好latex的规则后,可以自动识别 $$ / $ 包裹的公式,并将公式优雅的呈现在页面上。
1、引入并配置
我这里使用的 cdn 引入的方式,在vue项目的 index.html 中 <head> 下配置
<!-- 配置 MathJax -->
<script>
window.MathJax = {
tex: {
inlineMath: [['$', '$'], ['\\(', '\\)']],
displayMath: [['$$', '$$'], ['\\[', '\\]']]
},
svg: {
fontCache: 'global'
}
};
</script>
<script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js" async></script>
2、组件中使用
<template>
<div>
<div id="json-container">{{ formattedJson }}</div>
<!-- <div id="json-container">{{ this.parseAndFormatNestedJson(this.jsonString) }}</div> -->
</div>
</template>
mounted() {
this.$nextTick(() => {
MathJax.typeset();
});
3、配置样式
配置到这里,你会发现页面上的公式正常渲染了,但是字符串的格式却乱了,所以需要加一些样式,代码如下:
#json-container {
font-family: monospace;
white-space: pre-wrap;
}
至此,整个Json格式化与渲染就完成了,希望对做同样需求的你,能够有所帮助,附上完整代码。
- 配置文件 index.html
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
<!-- 配置 MathJax -->
<script>
window.MathJax = {
tex: {
inlineMath: [['$', '$'], ['\\(', '\\)']],
displayMath: [['$$', '$$'], ['\\[', '\\]']]
},
svg: {
fontCache: 'global'
}
};
</script>
<script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js" async></script>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
- 组件文件 Mathjax.vue
<template>
<div>
<div id="json-container">{{ formattedJson }}</div>
<!-- <div id="json-container">{{ this.parseAndFormatNestedJson(this.jsonString) }}</div> -->
</div>
</template>
<script>
export default {
name: 'JsonFormatter',
data() {
return {
jsonString: "{\"msg\":\"ok\",\"code\":20000,\"data\":{\"result\":[{\"probs\":0.99942553043365479,\"texts\":\"如图,菱形ABCD的边长为4,且A\",\"char_info\":[{\"pos\":[{\"x\":0,\"y\":33},{\"x\":22,\"y\":33},{\"x\":22,\"y\":60},{\"x\":0,\"y\":60}],\"char\":\"如\",\"type\":1},{\"pos\":[{\"x\":22,\"y\":33},{\"x\":43,\"y\":33},{\"x\":43,\"y\":60},{\"x\":22,\"y\":60}],\"char\":\"图\",\"type\":1},{\"pos\":[{\"x\":43,\"y\":33},{\"x\":68,\"y\":33},{\"x\":68,\"y\":60},{\"x\":43,\"y\":60}],\"char\":\",\",\"type\":1},{\"pos\":[{\"x\":68,\"y\":33},{\"x\":95,\"y\":33},{\"x\":95,\"y\":60},{\"x\":68,\"y\":60}],\"char\":\"菱\",\"type\":1},{\"pos\":[{\"x\":95,\"y\":33},{\"x\":118,\"y\":33},{\"x\":118,\"y\":60},{\"x\":95,\"y\":60}],\"char\":\"形\",\"type\":1},{\"pos\":[{\"x\":118,\"y\":33},{\"x\":140,\"y\":33},{\"x\":140,\"y\":60},{\"x\":118,\"y\":60}],\"char\":\"A\",\"type\":1},{\"pos\":[{\"x\":140,\"y\":33},{\"x\":157,\"y\":33},{\"x\":157,\"y\":60},{\"x\":140,\"y\":60}],\"char\":\"B\",\"type\":1},{\"pos\":[{\"x\":157,\"y\":33},{\"x\":174,\"y\":33},{\"x\":174,\"y\":60},{\"x\":157,\"y\":60}],\"char\":\"C\",\"type\":1},{\"pos\":[{\"x\":174,\"y\":33},{\"x\":194,\"y\":33},{\"x\":194,\"y\":60},{\"x\":174,\"y\":60}],\"char\":\"D\",\"type\":1},{\"pos\":[{\"x\":194,\"y\":33},{\"x\":216,\"y\":33},{\"x\":216,\"y\":60},{\"x\":194,\"y\":60}],\"char\":\"的\",\"type\":1},{\"pos\":[{\"x\":216,\"y\":33},{\"x\":240,\"y\":33},{\"x\":240,\"y\":60},{\"x\":216,\"y\":60}],\"char\":\"边\",\"type\":1},{\"pos\":[{\"x\":240,\"y\":33},{\"x\":263,\"y\":33},{\"x\":263,\"y\":60},{\"x\":240,\"y\":60}],\"char\":\"长\",\"type\":1},{\"pos\":[{\"x\":263,\"y\":33},{\"x\":285,\"y\":33},{\"x\":285,\"y\":60},{\"x\":263,\"y\":60}],\"char\":\"为\",\"type\":1},{\"pos\":[{\"x\":285,\"y\":33},{\"x\":301,\"y\":33},{\"x\":301,\"y\":60},{\"x\":285,\"y\":60}],\"char\":\"4\",\"type\":1},{\"pos\":[{\"x\":301,\"y\":33},{\"x\":321,\"y\":33},{\"x\":321,\"y\":60},{\"x\":301,\"y\":60}],\"char\":\",\",\"type\":1},{\"pos\":[{\"x\":321,\"y\":33},{\"x\":348,\"y\":33},{\"x\":348,\"y\":60},{\"x\":321,\"y\":60}],\"char\":\"且\",\"type\":1},{\"pos\":[{\"x\":348,\"y\":33},{\"x\":375,\"y\":33},{\"x\":375,\"y\":60},{\"x\":348,\"y\":60}],\"char\":\"A\",\"type\":1}],\"variance\":0.0010391805553808808,\"poses\":[{\"x\":0,\"y\":33},{\"x\":375,\"y\":33},{\"x\":375,\"y\":60},{\"x\":0,\"y\":60}]},{\"probs\":0.98136389255523682,\"texts\":\"$$ E \\\\bot B C $$于E,$$ A F \\\\bot C D $$于F,$$ \\\\angle B = 6 $$\",\"char_info\":[{\"pos\":[{\"x\":73,\"y\":69},{\"x\":96,\"y\":69},{\"x\":96,\"y\":96},{\"x\":73,\"y\":96}],\"char\":\"于\",\"type\":1},{\"pos\":[{\"x\":96,\"y\":69},{\"x\":115,\"y\":69},{\"x\":115,\"y\":96},{\"x\":96,\"y\":96}],\"char\":\"E\",\"type\":1},{\"pos\":[{\"x\":115,\"y\":69},{\"x\":125,\"y\":69},{\"x\":125,\"y\":96},{\"x\":115,\"y\":96}],\"char\":\",\",\"type\":1},{\"pos\":[{\"x\":231,\"y\":70},{\"x\":252,\"y\":70},{\"x\":252,\"y\":96},{\"x\":231,\"y\":96}],\"char\":\"于\",\"type\":1},{\"pos\":[{\"x\":252,\"y\":70},{\"x\":269,\"y\":70},{\"x\":269,\"y\":96},{\"x\":252,\"y\":96}],\"char\":\"F\",\"type\":1},{\"pos\":[{\"x\":269,\"y\":70},{\"x\":279,\"y\":70},{\"x\":279,\"y\":96},{\"x\":269,\"y\":96}],\"char\":\",\",\"type\":1}],\"variance\":0.048195969313383102,\"poses\":[{\"x\":0,\"y\":70},{\"x\":373,\"y\":69},{\"x\":373,\"y\":94},{\"x\":0,\"y\":94}]},{\"probs\":0.99968928098678589,\"texts\":\"$$ 0 ^ { \\\\circ } $$,则菱形ABCD的面积\",\"char_info\":[{\"pos\":[{\"x\":0,\"y\":104},{\"x\":41,\"y\":104},{\"x\":41,\"y\":133},{\"x\":0,\"y\":133}],\"char\":\",\",\"type\":1},{\"pos\":[{\"x\":41,\"y\":104},{\"x\":69,\"y\":104},{\"x\":69,\"y\":133},{\"x\":41,\"y\":133}],\"char\":\"则\",\"type\":1},{\"pos\":[{\"x\":69,\"y\":104},{\"x\":93,\"y\":104},{\"x\":93,\"y\":133},{\"x\":69,\"y\":133}],\"char\":\"菱\",\"type\":1},{\"pos\":[{\"x\":93,\"y\":104},{\"x\":117,\"y\":104},{\"x\":117,\"y\":133},{\"x\":93,\"y\":133}],\"char\":\"形\",\"type\":1},{\"pos\":[{\"x\":117,\"y\":104},{\"x\":139,\"y\":104},{\"x\":139,\"y\":133},{\"x\":117,\"y\":133}],\"char\":\"A\",\"type\":1},{\"pos\":[{\"x\":139,\"y\":104},{\"x\":157,\"y\":104},{\"x\":157,\"y\":133},{\"x\":139,\"y\":133}],\"char\":\"B\",\"type\":1},{\"pos\":[{\"x\":157,\"y\":104},{\"x\":173,\"y\":104},{\"x\":173,\"y\":133},{\"x\":157,\"y\":133}],\"char\":\"C\",\"type\":1},{\"pos\":[{\"x\":173,\"y\":104},{\"x\":192,\"y\":104},{\"x\":192,\"y\":133},{\"x\":173,\"y\":133}],\"char\":\"D\",\"type\":1},{\"pos\":[{\"x\":192,\"y\":104},{\"x\":215,\"y\":104},{\"x\":215,\"y\":133},{\"x\":192,\"y\":133}],\"char\":\"的\",\"type\":1},{\"pos\":[{\"x\":215,\"y\":104},{\"x\":239,\"y\":104},{\"x\":239,\"y\":133},{\"x\":215,\"y\":133}],\"char\":\"面\",\"type\":1},{\"pos\":[{\"x\":239,\"y\":104},{\"x\":269,\"y\":104},{\"x\":269,\"y\":133},{\"x\":239,\"y\":133}],\"char\":\"积\",\"type\":1}],\"variance\":0.00050142116378992796,\"poses\":[{\"x\":0,\"y\":104},{\"x\":269,\"y\":104},{\"x\":269,\"y\":133},{\"x\":0,\"y\":133}]},{\"probs\":0.92363280057907104,\"texts\":\"为___.\",\"char_info\":[{\"pos\":[{\"x\":0,\"y\":141},{\"x\":25,\"y\":141},{\"x\":25,\"y\":171},{\"x\":0,\"y\":171}],\"char\":\"为\",\"type\":1},{\"pos\":[{\"x\":25,\"y\":141},{\"x\":41,\"y\":141},{\"x\":41,\"y\":171},{\"x\":25,\"y\":171}],\"char\":\"_\",\"type\":1},{\"pos\":[{\"x\":41,\"y\":141},{\"x\":47,\"y\":141},{\"x\":47,\"y\":171},{\"x\":41,\"y\":171}],\"char\":\"_\",\"type\":1},{\"pos\":[{\"x\":47,\"y\":141},{\"x\":87,\"y\":141},{\"x\":87,\"y\":171},{\"x\":47,\"y\":171}],\"char\":\"_\",\"type\":1},{\"pos\":[{\"x\":87,\"y\":141},{\"x\":132,\"y\":141},{\"x\":132,\"y\":171},{\"x\":87,\"y\":171}],\"char\":\".\",\"type\":1}],\"variance\":0.15443108975887299,\"poses\":[{\"x\":0,\"y\":141},{\"x\":132,\"y\":141},{\"x\":132,\"y\":171},{\"x\":0,\"y\":171}]},{\"probs\":0.998046875,\"texts\":\"A\",\"char_info\":[{\"pos\":[{\"x\":64,\"y\":171},{\"x\":87,\"y\":171},{\"x\":87,\"y\":186},{\"x\":64,\"y\":186}],\"char\":\"A\",\"type\":1}],\"variance\":0,\"poses\":[{\"x\":64,\"y\":171},{\"x\":87,\"y\":171},{\"x\":87,\"y\":186},{\"x\":64,\"y\":186}]},{\"probs\":0.99951171875,\"texts\":\"B D\",\"char_info\":[{\"pos\":[{\"x\":0,\"y\":216},{\"x\":11,\"y\":216},{\"x\":11,\"y\":232},{\"x\":0,\"y\":232}],\"char\":\"B\",\"type\":1},{\"pos\":[{\"x\":12,\"y\":216},{\"x\":143,\"y\":216},{\"x\":143,\"y\":232},{\"x\":12,\"y\":232}],\"char\":\" \",\"type\":1},{\"pos\":[{\"x\":144,\"y\":216},{\"x\":159,\"y\":216},{\"x\":159,\"y\":232},{\"x\":144,\"y\":232}],\"char\":\"D\",\"type\":1}],\"variance\":0,\"poses\":[{\"x\":0,\"y\":216},{\"x\":159,\"y\":216},{\"x\":159,\"y\":232},{\"x\":0,\"y\":232}]},{\"probs\":1,\"texts\":\"E F\",\"char_info\":[{\"pos\":[{\"x\":32,\"y\":243},{\"x\":45,\"y\":243},{\"x\":45,\"y\":259},{\"x\":32,\"y\":259}],\"char\":\"E\",\"type\":1},{\"pos\":[{\"x\":46,\"y\":243},{\"x\":107,\"y\":243},{\"x\":107,\"y\":259},{\"x\":46,\"y\":259}],\"char\":\" \",\"type\":1},{\"pos\":[{\"x\":108,\"y\":243},{\"x\":122,\"y\":243},{\"x\":122,\"y\":259},{\"x\":108,\"y\":259}],\"char\":\"F\",\"type\":1}],\"variance\":0,\"poses\":[{\"x\":32,\"y\":243},{\"x\":122,\"y\":243},{\"x\":122,\"y\":259},{\"x\":32,\"y\":259}]},{\"probs\":0.99853515625,\"texts\":\"C\",\"char_info\":[{\"pos\":[{\"x\":69,\"y\":261},{\"x\":94,\"y\":261},{\"x\":94,\"y\":277},{\"x\":69,\"y\":277}],\"char\":\"C\",\"type\":1}],\"variance\":0,\"poses\":[{\"x\":69,\"y\":261},{\"x\":94,\"y\":261},{\"x\":94,\"y\":277},{\"x\":69,\"y\":277}]}],\"rotate\":0,\"images\":[{\"poses\":[{\"x\":0,\"y\":168},{\"x\":167,\"y\":168},{\"x\":167,\"y\":279},{\"x\":0,\"y\":279}]}],\"single_box\":{\"print_text\":[{\"texts\":\"如图,菱形ABCD的边长为4,且A\",\"Confidence\":0.99942553043365479,\"stdofconfi\":0.0010391805553808808,\"poses\":[{\"x\":0,\"y\":33},{\"x\":375,\"y\":33},{\"x\":375,\"y\":60},{\"x\":0,\"y\":60}]},{\"texts\":\"$$ 0 ^ { \\\\circ } $$,则菱形ABCD的面积\",\"Confidence\":0.99968928098678589,\"stdofconfi\":0.00050142116378992796,\"poses\":[{\"x\":0,\"y\":104},{\"x\":269,\"y\":104},{\"x\":269,\"y\":133},{\"x\":0,\"y\":133}]},{\"texts\":\"为___.\",\"Confidence\":0.92363280057907104,\"stdofconfi\":0.15443108975887299,\"poses\":[{\"x\":0,\"y\":141},{\"x\":132,\"y\":141},{\"x\":132,\"y\":171},{\"x\":0,\"y\":171}]},{\"texts\":\"D\",\"Confidence\":1,\"stdofconfi\":0,\"poses\":[{\"x\":144,\"y\":216},{\"x\":159,\"y\":216},{\"x\":159,\"y\":232},{\"x\":144,\"y\":232}]},{\"texts\":\"B\",\"Confidence\":0.99951171875,\"stdofconfi\":0,\"poses\":[{\"x\":0,\"y\":216},{\"x\":11,\"y\":216},{\"x\":11,\"y\":232},{\"x\":0,\"y\":232}]},{\"texts\":\"于E,\",\"Confidence\":0.99951171875,\"stdofconfi\":0.00048828125,\"poses\":[{\"x\":73,\"y\":69},{\"x\":125,\"y\":69},{\"x\":125,\"y\":96},{\"x\":73,\"y\":96}]},{\"texts\":\"于F,\",\"Confidence\":0.96321612596511841,\"stdofconfi\":0.047707688063383102,\"poses\":[{\"x\":231,\"y\":70},{\"x\":279,\"y\":70},{\"x\":279,\"y\":96},{\"x\":231,\"y\":96}]},{\"texts\":\"F\",\"Confidence\":0.99951171875,\"stdofconfi\":0,\"poses\":[{\"x\":108,\"y\":243},{\"x\":122,\"y\":243},{\"x\":122,\"y\":259},{\"x\":108,\"y\":259}]},{\"texts\":\"E\",\"Confidence\":1,\"stdofconfi\":0,\"poses\":[{\"x\":32,\"y\":243},{\"x\":45,\"y\":243},{\"x\":45,\"y\":259},{\"x\":32,\"y\":259}]},{\"texts\":\"A\",\"Confidence\":0.998046875,\"stdofconfi\":0,\"poses\":[{\"x\":64,\"y\":171},{\"x\":87,\"y\":171},{\"x\":87,\"y\":186},{\"x\":64,\"y\":186}]},{\"texts\":\"C\",\"Confidence\":0.99853515625,\"stdofconfi\":0,\"poses\":[{\"x\":69,\"y\":261},{\"x\":94,\"y\":261},{\"x\":94,\"y\":277},{\"x\":69,\"y\":277}]}],\"print_formula\":[{\"texts\":\"$$ E \\\\bot B C $$\",\"Confidence\":0.99000000953674316,\"stdofconfi\":0,\"poses\":[{\"x\":0,\"y\":70},{\"x\":74,\"y\":70},{\"x\":74,\"y\":94},{\"x\":0,\"y\":94}]},{\"texts\":\"$$ A F \\\\bot C D $$\",\"Confidence\":0.99000000953674316,\"stdofconfi\":0,\"poses\":[{\"x\":138,\"y\":70},{\"x\":233,\"y\":70},{\"x\":233,\"y\":93},{\"x\":138,\"y\":94}]},{\"texts\":\"$$ \\\\angle B = 6 $$\",\"Confidence\":0.99000000953674316,\"stdofconfi\":0,\"poses\":[{\"x\":293,\"y\":70},{\"x\":373,\"y\":69},{\"x\":373,\"y\":94},{\"x\":293,\"y\":94}]},{\"texts\":\"$$ 0 ^ { \\\\circ } $$\",\"Confidence\":0.99000000953674316,\"stdofconfi\":0,\"poses\":[{\"x\":0,\"y\":107},{\"x\":21,\"y\":107},{\"x\":21,\"y\":128},{\"x\":0,\"y\":128}]}]}}}"
};
},
computed: {
formattedJson() {
return this.parseAndFormatNestedJson(this.jsonString);
}
},
methods: {
parseAndFormatNestedJson(jsonString) {
const recursiveParse = jsonStr => {
try {
let parsedData = JSON.parse(jsonStr);
if (typeof parsedData === 'string') {
return recursiveParse(parsedData);
}
for (let key in parsedData) {
if (typeof parsedData[key] === 'string') {
try {
parsedData[key] = recursiveParse(parsedData[key]);
} catch (e) {
// 保留原始字符串
}
}
}
return parsedData;
} catch (e) {
return jsonStr;
}
};
const parsedResult = recursiveParse(jsonString);
return this.formatJson(parsedResult).replace(/\$\$/g, '$').replace(/\\\\/g, '\\');
},
formatJson(obj, level = 0) {
const spaces = ' '.repeat(level);
if (typeof obj !== 'object' || obj === null) {
return JSON.stringify(obj);
}
if (Array.isArray(obj)) {
const items = obj.map(item => `\n${spaces} ${this.formatJson(item, level + 1)}`);
return `[${items.join(', ')}\n${spaces}]`;
} else {
const entries = Object.entries(obj).map(([key, value]) =>
`\n${spaces} "${key}": ${this.formatJson(value, level + 1)}`
);
return `{${entries.join(', ')}\n${spaces}}`;
}
}
},
mounted() {
this.$nextTick(() => {
MathJax.typeset();
});
}
};
</script>
<style scoped>
#json-container {
font-family: monospace;
white-space: pre-wrap;
}
</style>