1、JSON语法规则:
JSON的语法可以表示以下三种类型的值:简单值,对象和数组.
- 简单值可以在JSON中表示字符串, 数值, 布尔值和null,在JSON中不能使用数值为NaN,也不能为Infinity,undefined也不能使用.字符串必须使用双引号.
- 对象可以拆分为左大括号,键值对,右大括号.
- 数组拆分为左中括号,数组元素,右中括号.
详情如下:
简单值 := <字符串> | <数字> | <逻辑值> | <null>
数组元素 := <简单值> | <对象> | <数组>
键值对 := <字符串>: (<数组> | <对象> | <简单值>)
可嵌套对象<left, element, right> := <left> ( <element> ("," <element>)* ){0, 1} <right>
数组 := 可嵌套对象<"[", <数组元素>, "]">
对象 := 可嵌套对象<"{", <键值对>, "}">
JSON := 数组 | 对象 | 简单值
2、案例说明:
以JSON格式的字符串{"asd":1}转化为JSON对象.
首先进行分词,"{"、""asd""、":"、"1"、"}",能判断这是一个JSON对象,里面有一个键值对.键值对内遇到一对双引号包裹的定义为字符串,冒号后面的数字1定义为数字.拆分为:
[{"content": "{", "type": "{"}, {"content": "asd", "type": "字符串"}, {"content": ":", "type": ":"}, {"content": "1", "type": "数字"}, {"content": "}", "type": "}"}]
然后解析为{"asd": 1}.
3、实现思路:
分词
首先定义类型格式:
export enum 令牌类型 {
左大括号 = "{",
右大括号 = "}",
左中括号 = "[",
右中括号 = "]",
引号 = "'",
逗号 = ",",
冒号 = ":",
数字 = "数字",
字符串 = "字符串",
逻辑值 = "逻辑值",
null = "null",
}
然后实现分词函数:
export interface 令牌 {
content: string;
type: 令牌类型;
index: number
}
export interface 解析令牌响应 {
index_increment: number,
result: string,
type: 令牌类型,
error?: string,
}
class DepthLimiter {
public current_loop_depth: number = 0;
public readonly MAX_LOOP_DEPTH = 1000;
public check_loop_depth() {
if (this.current_loop_depth > this.MAX_LOOP_DEPTH) {
throw `递归深度过大: ${this.current_loop_depth}`;
}
this.current_loop_depth += 1;
}
public clear() {
this.current_loop_depth = 0;
}
}
const depth_limiter = new DepthLimiter()
export const 是否是数字开头 = String.prototype.includes.bind(`-.0123456789`)
export function 是不是n开头(a: string) {
return a === "n";
}
export function 是不是逻辑值开头(a: string) {
return a === "t" || a === "f";
}
export function 是否是数字(a: string) {
if (!"0123456789".includes(a)) {
return false;
}
return true;
}
export function 是否是字符串(a: string) {
if (a === "") return true;
const 第一位 = a[0];
const 最后一位 = a[a.length - 1];
if (a.length >= 2 && 第一位 === '"' && 最后一位 === '"') {
return true;
}
return false;
}
export function 分词(a: string): Array<令牌> {
const res: 令牌[] = [];
for (let i = 0; i < a.length;) {
const char = a[i];
let r: 解析令牌响应;
if (" \t\n\r".includes(char)) {
i += 1;
} else if (是否是数字开头(char)) {
r = 解析数字(a.slice(i))
res.push({
content: r.result,
type: r.type,
index: i
})
i += r.index_increment
} else if (char === `{`) {
res.push({
content: char,
type: 令牌类型.左大括号,
index: i
});
i += 1;
} else if (char === `}`) {
res.push({
content: char,
type: 令牌类型.右大括号,
index: i
});
i += 1;
} else if (char === `[`) {
res.push({
content: char,
type: 令牌类型.左中括号,
index: i
});
i += 1;
} else if (char === `]`) {
res.push({
content: char,
type: 令牌类型.右中括号,
index: i
});
i += 1;
} else if (char === `,`) {
res.push({
content: char,
type: 令牌类型.逗号,
index: i
});
i += 1
} else if (char === `:`) {
res.push({
content: char,
type: 令牌类型.冒号,
index: i
});
i += 1;
} else if (char === `"`) {
r = 解析字符串(a.slice(i));
res.push({
content: r.result,
type: r.type,
index: i
})
i += r.index_increment;
} else if (是不是n开头(char)) {
r = 解析null(a.slice(i));
res.push({
content: r.result,
type: r.type,
index: i
})
i += r.index_increment;
} else if (是不是逻辑值开头(char)) {
r = 解析逻辑值(a.slice(i));
res.push({
content: r.result,
type: r.type,
index: i
})
i += r.index_increment;
} else {
throw `语法错误: 未知的符号 ${char}`;
}
if (r?.error) {
throw `error: ${r.error} 于位置 ${i}`;
}
}
return res;
}
解析
解析数字
现在来实现解析数字函数:
数字分为整数,浮点数,带有e/E的数字.
比如:
1 -1 1.1 -2.0 234 123 1e2 1E2 20e3 -3e8 1.2e3
具体如下:
解析整数 1 -1 2 0
解析浮点数 := 解析整数 . 解析正整数
解析普通数字 := 解析整数 | 解析浮点数
解析科学计数法 := 解析普通数字 e|E 解析正整数
解析数字 := 解析普通数字 | 解析科学计数法
具体实现:
function 解析正整数(s: string, dont_throw_when_zero = false): string {
let res = '';
for (const c of s) {
if ("0123456789".includes(c)) {
res += c;
} else {
break;
}
}
if (dont_throw_when_zero) {
return res;
}
if (res.length && res[0] === "0") {
if (res.length === 1) {
return '0';
} else {
throw `长整数不能以0开头`;
}
}
return res;
}
export function 解析整数(a: string): string {
let res = '';
let to_parsed = a;
if (to_parsed[0] === "-") {
to_parsed = to_parsed.slice(1);
res += '-';
}
const 正整数解析结果 = 解析正整数(to_parsed);
if (正整数解析结果 === "") {
throw `不正确的数字${a}`;
} else {
res += 正整数解析结果;
}
if (res.length === 1 && res[0] === "-") {
throw `只有一个负号`;
}
return res;
}
export function 解析普通数字(a: string): string {
const prefix = 解析整数(a);
let i = prefix.length;
if (a[i] !== `.`) {
return prefix;
}
const suffix = 解析正整数(a.slice(i + 1), true);
if (suffix === "") {
return prefix;
}
return prefix + '.' + suffix;
}
export function 解析科学计数法(a: string): string {
const prefix = 解析普通数字(a);
let i = prefix.length;
if (a.length == 0) {
return prefix;
}
if (a[i] === undefined) {
return prefix;
}
if (a[i].toLowerCase() !== `e`) {
return prefix;
}
const suffix = 解析整数(a.slice(i + 1));
if (suffix === "") {
throw `e后面应有数字`;
}
return prefix + 'e' + suffix;
}
function 解析数字(a: string): 解析令牌响应 {
const res: 解析令牌响应 = {
index_increment: 0,
result: "",
type: 令牌类型.数字
}
const temp = 解析科学计数法(a);
res.result = temp;
res.index_increment = temp.length;
return res;
}
解析字符串
export function 解析字符串(a: string): 解析令牌响应 {
const res: 解析令牌响应 = {
index_increment: 1,
result: ``,
type: 令牌类型.字符串
}
for (const i of a.slice(1)) {
if (i !== `"`) {
if ('\r\n\b\t'.includes(i)) {
throw `字符串不应该有空白符`;
}
res.result += i;
res.index_increment += 1;
} else {
res.index_increment += 1;
break;
}
}
if (res.index_increment === 1) {
res.error = "字符串异常停止"
throw `字符串异常停止 ${res}`;
}
return res;
}
解析逻辑值
export function 解析逻辑值(a: string): 解析令牌响应 {
const res: 解析令牌响应 = {
index_increment: 0,
result: ``,
type: 令牌类型.逻辑值
}
for (let i = 0; i < a.length;) {
if (a.slice(i, i + 4) === "true") {
res.result = "true";
res.index_increment = 4;
i += 4;
return res;
} else if (a.slice(i, i + 5) === "false") {
res.result = "false";
res.index_increment = 5;
i += 5;
return res;
} else {
throw `不正确的逻辑值${a}`;
}
}
return res;
}
解析null
export function 解析null(a: string): 解析令牌响应 {
const res: 解析令牌响应 = {
index_increment: 0,
result: ``,
type: 令牌类型.null
}
if (a.slice(0, 4) === "null") {
res.result = null;
res.index_increment = 4;
} else {
throw `不正确的null`;
}
return res;
}
解析数组
export interface 解析对象响应 {
index_increment: number,
result: any
}
function 解析数组(arr: Array<令牌>): 解析对象响应 {
const res: 解析对象响应 = {
index_increment: 1,
result: [],
}
let flag = true;
for (let i = 1; i < arr.length;) {
const curr_token = arr[i];
if (!flag && curr_token.type !== ']') {
if (curr_token.type !== ",") {
throw `缺少逗号!`;
}
i++;
}
if (curr_token.type === ']') {
res.index_increment = i + 1;
break;
}
const current_array_res = 通用解析(arr.slice(i));
res.result.push(current_array_res.result);
// console.log("12",res.result);
i += current_array_res.index_increment;
flag = false;
// i的结果最后要返回给res的i的增量,这样主函数才能拿到i的增加数
res.index_increment = i;
}
if (arr.length === 1 && arr[0].type === '[') {
throw `语法错误: 预期外的字符 [`;
}
if (arr[res.index_increment - 1].type !== ']') {
throw `语法错误: 数组未能正常停止`;
}
return res;
}
解析键值对
export function 解析pair(arr: 令牌[]): 解析对象响应 {
// "a": 2, "asd": ["zxc"]
const res: 解析对象响应 = {
index_increment: 2,
result: {
key: undefined,
value: undefined
}
}
if (arr.length < 3) {
throw `key of pair 长度至少为三`;
}
if (arr[0].type !== 令牌类型.字符串) {
throw `key of pair must be string`;
}
res.result.key = arr[0].content;
if (arr[1].type !== 令牌类型.冒号) {
throw `键值对连接必须为冒号`;
}
const value_res = 通用解析(arr.slice(2));
res.index_increment += value_res.index_increment;
res.result.value = value_res.result;
return res;
}
解析对象
export function 解析对象(arr: Array<令牌>): 解析对象响应 {
const res: 解析对象响应 = {
index_increment: 1,
result: {},
}
for (let i = 1; i < arr.length;) {
const token = arr[i];
if (arr[i].type === `,`) {
res.index_increment += 1;
i += 1;
} else if (arr[i].type === `}`) {
res.index_increment += 1;
break;
}
const pair = 解析pair(arr.slice(res.index_increment));
res.result[pair.result.key] = pair.result.value;
res.index_increment += pair.index_increment;
i = res.index_increment;
}
if (arr.length === 1 && arr[0].type === '{') {
throw `语法错误: 预期外的字符 {`;
}
if (arr[res.index_increment - 1].type !== '}') {
throw `语法错误: 对象未能正常停止`;
}
if (arr[res.index_increment - 2].type === ",") {
throw `语法错误: 对象不应当有裸逗号`;
}
return res;
}
通用解析
export function 通用解析(tokens: 令牌[]): 解析对象响应 {
const arr = tokens;
const res: 解析对象响应 = {
index_increment: 0,
result: undefined
}
const curr_token = arr[res.index_increment];
depth_limiter.check_loop_depth();
if (curr_token.type === "[") {
const current_array_res = 解析数组(arr);
res.result = current_array_res.result;
res.index_increment = current_array_res.index_increment;
} else if (curr_token.type === "{") {
const current_array_res = 解析对象(arr);
res.result = current_array_res.result;
res.index_increment = current_array_res.index_increment;
}
else if (curr_token.type === "字符串") {
res.result = curr_token.content;
res.index_increment = 1;
} else if (curr_token.type === "数字") {
if (curr_token.content === "") {
throw `数字错误`;
}
res.result = Number(curr_token.content);
res.index_increment = 1;
} else if (curr_token.type === '}') {
throw `语法错误: 预期外的字符 ${curr_token.content} 在位置 ${curr_token.index}`;
} else if (curr_token.type === "null") {
res.result = curr_token.content
res.index_increment = 1;
} else if (curr_token.type === '逻辑值') {
res.result = curr_token.content === "true" ? true : false;
res.index_increment = 1;
} else {
throw `未知的类型 ${curr_token}`;
}
return res;
}
export function 解析(tokens: 令牌[]): 解析对象响应 {
const res: 解析对象响应 = {
index_increment: 0,
result: undefined
}
if (tokens.length === 0) {
throw `空的令牌数组`
}
depth_limiter.clear();
const curr_res = 通用解析(tokens.slice(res.index_increment));
res.index_increment += curr_res.index_increment;
res.result = curr_res.result;
if (res.index_increment !== tokens.length) {
throw `JSON应该只有一个主体`;
}
return res.result;
}
4、参考:
JSON解析器能够正确接受几乎所有符合RFC的JSON, 拒绝unicode以外的几乎所有JSON(测试套件地址:链接 )
使用jest测试框架,测试套件.结果如下: