我个人主要是做一些后端的工作,比如php、python、c之类的,c比较少,最主要的是php,不过我非常喜欢js,所以经常会愿意去写一些小玩意自娱自乐。
今天在测试接口的时候,使用原生js的XMLHttpRequest去请求,直接使用document….innerHTML输出到页面,因为没有浏览器的json格式化没有生效,所以接口响应的json数据就看起来很不舒服。
于是乎,我就想起了为啥我自己不能实现一个,应该不是很难吧。
我仔细思考了一下,这玩意不就是遇到大括号、中括号和中括号就换行吗,每行还有个缩进,人家再高级一点的还有颜色,折叠功能、缩进对齐线之类的,于是我觉得一步一步来,先把格式化和颜色实现出来,后面的折叠、动画和缩进对齐线再慢慢做。
电影有句话说的好啊:“步子迈大了,咔,容易扯着蛋”。
扯了这老些没用的,下面开始我的正经思路:
合法的JSON字符串是一个单行字符串,边界符号是“{} []”,键值对之间是以英文逗号“,”作为分隔,键和值之间是用英文冒号“:”进行分隔。JSON内的字符串必须以双引号包括在外侧,数字类型或布尔类型可以不使用双引号包括。逗号分隔符的后面必须至少存在一个键值对(即末尾的键值对的后面不能有逗号分隔符了,这里的逗号指的是键值对分隔符,而不是指值内的逗号字符串)。
看看别人优秀的是什么样子的。
从某平台的json格式化服务截取
可以发现在“{, [”的后面都会有一个换行,每个键值对的后面都有一个换行,在符号“], 和 }, ”的后面都会有一个换行。
而且格式化后的JSON是有“结构区块”的,从缩进来区分不同的结构块,这一点有点像python,不过这有点牵强,格式化这样做的目的是为了能够很清晰的查看JSON的结构,与python的目的是有本质的区别的。
从截图来看,只要遇到一个“{ 或 [”,就要出现缩进,而且可以看出,缩进是随着遇到的个数增加的,这是成正比啊。而且只要遇到“]或}”,缩进就要少一个。
说到这里是不是就有感觉了,缩进就是在匹配括号啊,这匹配括号在逆波兰式里的操作啊,不就是基础的数据结构“栈”吗。
那我对整个字符串进行遍历判断是不就能做到了,那有了思路就可以动手了,能动手我就不在这里扯?了。
<!-- 这里在页面放一个pre标签,让输入的结构按照我们需要的形式展现 -->
<pre id="json"></pre>
假设我有一个jsonStr,我想要格式化它。
var jsonStr = '{"state":{"code":0,"success":true,"ok":1},"body":[{"count":2394,"dbName":"star_all"},{"count":133,"dbName":"star"},{"count":7,"dbName":"zy"},{"count":1,"dbName":"gordon_test"}]}';
function format(str){
var stack = []; //栈-用于括号匹配
var tmpStr = ''; //新格式化JSON字符串
var len = str.length; //原始JSON长度
//遍历每一个字符
for (let i = 0; i < len; i++) {
//当遇到结构块起始结构
if (str[i] == '{' || str[i] === '[') {
//起始结构后面直接换行
tmpStr += str[i] + "\n";
//入栈
stack.push(str[i]);
//这里的意思是结构块起始的下一行开始就会有一个缩进,缩进量与遇到的结构块起始符个数成正比1:1
tmpStr += "\t".repeat(stack.length);
}
//当遇到结构块结束符
else if (str[i] == ']' || str[i] === '}') {
//因为本身JSON格式是固定的,所以括号一定是成对的,这里先不考虑错误的json数据
//遇到结束符就退栈,
stack.pop();
//结束符本身输出到下一行,并减少一个缩进
tmpStr += "\n"+"\t".repeat(stack.length) + str[i];
}
//当遇到逗号的时候
else if (str[i] == ',') {
//逗号后方直接换行,以及下一行的缩进处理
tmpStr += str[i] + "\n" + "\t".repeat(stack.length);
}
else {
//其他字符直接复制
tmpStr += str[i];
}
}
return tmpStr;
}
返回的数据放到<pre>标签内
document.querySelector('#json').innerHTML = format(jsonStr);
输出的效果如下图
制表符\t稍微有点远,当然使用4个 也可以,别纠结~
这黑白色的不美观,那就给上个色呗,看看人家的,括号一个颜色,键值对一个颜色,值一个颜色,我不能抄它的,我觉得字符串,数字、布尔都分别用一种颜色就行,这个实现就都能实现了。
那既然需要分开使用不同的颜色,那么必然就涉及CSS了,每个结构就得有HTML结构了,我直接用正则是不是就解决了。
先写个CSS样式把
.bold{
font-weight: 900;
}
.string-color {
color: darkred;
}
.token {
color: darkgreen;
}
.number {
color: green;
}
.bool {
color: orange;
}
//使用捕获,匹配全部的边界符号,class使用token
tmpStr = tmpStr.replace(/([\{\[\]\}])/g, '<span class="token bold">$1</span>');
//使用零宽断言和捕获,匹配全部的两侧是双引号的字符串,class使用string
tmpStr = tmpStr.replace(/(?<=\")(\w+)(?=\")/g, '<span class="string bold">$1</span>');
//使用零宽断言,匹配全部的前面位置是冒号,后面是逗号或换行的数字类型值
tmpStr = tmpStr.replace(/(?<=\:)(\d+)(?=[\,\n])/g, '<span class="number bold">$1</span>');
//同理匹配布尔,
tmpStr = tmpStr.replace(/(?<=\:)(true|false)(?=[\,\n])/g, '<span class="bool bold">$1</span>');
零宽断言就是匹配一个位置,分负向零宽断言和正向零宽断言,不知道的可以搜索一下。
效果还行
到这里就初步完成了一个还算能入眼的基础JSON格式化小方法。后续再加个闪电爆炸的特效,下次再分享~