思路
字符串转 token 后解析成对应对象或者数组
string => tokens => object
代码实现
token.js
定义不同类型数据类型
// 引入 Token 类所需的 TokenType
const Type = {
auto: 0, // auto 的时候, c 是 : , { } [] 其中之一, 要自己判断
colon: 1, // :
comma: 2, // ,
braceLeft: 3, // {
braceRight: 4, // }
bracketLeft: 5, // [
bracketRight: 6, // ]
keyword: 7, // true false null
number: 8, // 123
string: 9, // "name"
};
class Token {
constructor(token_type, value) {
const d = {
":": Type.colon,
",": Type.comma,
"{": Type.braceLeft,
"}": Type.braceRight,
"[": Type.bracketLeft,
"]": Type.bracketRight,
};
if (token_type === Type.auto) {
this.type = d[value];
} else {
this.type = token_type;
}
this.value = value;
}
toString() {
return `type: ${this.type}, len: ${String(this.value).length}, value: "${
this.value
}"`;
}
}
tokenList.js 将文本转 token
const tokenList = (code) => {
let i = 0;
const length = code.length;
const tokens = [];
let is_open = true;
while (i < length) {
const c = code[i];
// console.log("ccccc", `(${c})`);
i++;
if (isSpaces(c)) {
continue;
} else if (isSpecialChar(c)) {
const t = new Token(Type.auto, c);
tokens.push(t);
} else if (c === '"' && is_open) {
// 吃字符串
const [result, index] = stringEnd(code, i);
if (index !== -1) {
const t = new Token(Type.string, result);
i = index;
tokens.push(t);
is_open = !is_open;
}
} else if (c === '"' && !is_open) {
is_open = !is_open;
continue;
} else if (isDigit(c)) {
// 吃数字
const [value, offset] = numberEnd(code, i);
const t = new Token(Type.number, value);
i += offset;
tokens.push(t);
} else if (isKeywordChar(c)) {
// true false null
const kvs = {
t: "true",
f: "false",
n: "null",
};
let value = kvs[c];
const t = new Token(Type.keyword, value);
tokens.push(t);
i += kvs[c].length;
} else {
console.log("错误", c, code.slice(i, i + 10));
return;
}
}
return tokens;
};
tokenParse.js
/**
* 从 Token 列表中解析出 JSON 对象或数组。
*
* @param {Token[]} ts - 要解析的 Token 列表。
* @returns {object|array} - 解析结果,可能是 JSON 对象或数组。
*/
const parse = (ts) => {
const t = ts[0];
ts.shift();
if (t.type === Type.braceLeft) {
const obj = {};
while (ts[0].type !== Type.braceRight) {
const k = ts[0];
// 确保 k.type 必须是 string
// 确保 _colon 必须是 colon
ts.shift();
ts.shift();
const v = parse(ts);
obj[k.value] = v.value || v;
// 吃一个 逗号
const _comma = ts[0];
if (_comma.type === Type.comma) {
ts.shift();
}
}
// 结束 删除末尾的 '}'
ts.shift();
return obj;
} else if (t.type === Type.bracketLeft) {
const l = [];
while (ts[0].type !== Type.bracketRight) {
const v = parse(ts);
// 吃一个 逗号
const _comma = ts[0];
if (_comma.type === Type.comma) {
ts.shift();
}
l.push(v.value);
}
// 删除末尾的 ']'
ts.shift();
return l;
} else {
// console.log('value: ', t);
return t;
}
};
tool.js
/**
* 从字符串中提取数字的长度,从指定的偏移量开始。
*
* @param {string} s1 - 要解析的字符串。
* @param {number} offset - 开始解析的位置。
* @returns {number|undefined} - 返回数字的长度,或者如果解析错误则返回 undefined。
*/
const numberEnd = (s1, offset) => {
// 定义包含数字字符的字符串
const digits = "1234567890";
// 从指定的偏移量开始逐字符检查
for (let i = 0; i < s1.length - offset; i++) {
const c = s1[offset + i];
// 如果当前字符不是数字字符,则返回当前位置 i,即数字的长度
if (!digits.includes(c)) {
let value = parseInt(s1.slice(offset - 1, i + offset));
return [value, i];
}
}
// 如果无法解析数字,则输出错误消息并返回 undefined
console.log("错误, 数字解析错误");
return undefined;
};
/**
* 从字符串中提取子字符串,直到遇到双引号结束,处理转义字符。
*
* @param {string} s1 - 要解析的字符串。
* @param {number} offset - 开始解析的位置。
* @returns {[string, number]|undefined} - 返回提取的子字符串和结束位置的数组,或者如果解析错误则返回 undefined。
*/
const stringEnd = (s1, offset) => {
// 定义转义字符及其对应的映射关系
const bs = {
b: "\b",
f: "\f",
n: "\n",
r: "\r",
t: "\t",
"/": "/",
'"': '"',
"\\": "\\",
};
// 初始化结果字符串和游标位置
let res = "";
let i = offset;
// 从指定的偏移量开始逐字符检查
while (i < s1.length) {
const a = s1[i];
// 如果遇到双引号,表示字符串结束,返回结果字符串和当前位置
if (a === '"') {
return [res, i];
} else if (a === "\\") {
// 如果遇到转义字符
const b = s1[i + 1]; // 获取转义字符后的字符
if (b in bs) {
// 如果是合法的转义字符
res += bs[b]; // 将转义字符替换为其映射的字符
i += 2; // 跳过转义字符和其后面的字符
} else {
console.log("** 错误, 不合法的转义字符: " + a + b);
const example = '\\b \\f \\n \\r \\t \\/ \\" \\\\';
console.log("合法的转义字符是: " + example);
return ["", -1]; // 返回空字符串和 -1 表示解析错误
}
} else {
res += a; // 将当前字符添加到结果字符串中
i += 1; // 移动游标位置到下一个字符
}
}
// 如果字符串未能正确结束,则输出错误消息并返回 undefined
console.log("错误, 字符串解析错误");
return undefined;
};
/**
* 检查给定的字符是否为空格字符(包括空格、制表符、换行符等)。
*
* @param {string} char - 要检查的字符。
* @returns {boolean} - 如果字符是空格字符,则返回 true;否则返回 false。
*/
const isSpaces = (char) => {
const spaces = " \b\f\n\r\t";
return spaces.includes(char);
};
/**
* 检查给定的字符是否是特定字符集合中的一个。
*
* @param {string} char - 要检查的字符。
* @returns {boolean} - 如果字符是特定字符集合中的一个,则返回 true;否则返回 false。
*/
const isSpecialChar = (char) => {
// 定义特定字符集合
const specialChars = ":,[]{}";
// 使用字符串的 includes 方法检查给定字符是否在特定字符集合中
return specialChars.includes(char);
};
/**
* 检查给定的字符是否是数字字符。
*
* @param {string} char - 要检查的字符。
* @returns {boolean} - 如果字符是数字字符,则返回 true;否则返回 false。
*/
const isDigit = (char) => {
// 定义包含数字字符的字符串
const digits = "0123456789";
// 使用字符串的 includes 方法检查给定字符是否在数字字符字符串中
return digits.includes(char);
};
/**
* 检查给定的字符是否是特定字符集合中的一个。
*
* @param {string} char - 要检查的字符。
* @returns {boolean} - 如果字符是特定字符集合中的一个,则返回 true;否则返回 false。
*/
const isKeywordChar = (char) => {
// 定义特定字符集合
const keywordChars = "tfn";
// 使用字符串的 includes 方法检查给定字符是否在特定字符集合中
return keywordChars.includes(char);
};
main.js
let t0 = `
{
"s1": "xigua",
"s2": "a\bb\fc\nd\re\tf\\/hi",
"num1": 11,
"bool": true,
"null": null,
"arr1": [1, 2, 3],
"obj": {
"bool2": false,
"arr2": [4, 5, 6],
"num2": 123
}
}
`;
const ts = tokenList(t0);
console.log("ts", ts);
const o = parse(ts);
console.log("o", o);
index.html
<!DOCTYPE html>
<html lang="en">
<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" />
<title>json parse</title>
</head>
<body>
<script src="./token.js"></script>
<script src="./tool.js"></script>
<script src="./tokenList.js"></script>
<script src="./jsonParse.js"></script>
<script src="./main.js"></script>
</body>
</html>
上面目录结构
├── index.html
├── main.js
├── token.js
├── tokenList.js
└── tokenParse.js
└── tool.js