JSON.parse 实现
描述
JSON.parse() 将 JSON 字符串解析为 JavaScript 值。
实现方式
JSON.parse 有两种实现方式:
- eval 方式:使用
eval()执行(简单但有安全隐患) - 正则方式:手动解析字符串(复杂但安全)
实现代码
方式一:eval 方式(简单实现)
js
JSON.myParse = function (jsonString) {
if (typeof jsonString !== "string") {
return jsonString;
}
// 使用 eval 执行,注意安全风险
// 在实际使用中推荐使用方式二
return eval("(" + jsonString + ")");
};方式二:正则解析(完整实现)
js
JSON.myParse = function (text, reviver) {
if (typeof text !== "string") {
return null;
}
// 去除前后空白
text = text.trim();
// 使用递归下降解析器
let index = 0;
function parseValue() {
skipWhitespace();
if (index >= text.length) {
throw new Error("Unexpected end of input");
}
const ch = text[index];
if (ch === "{") {
return parseObject();
}
if (ch === "[") {
return parseArray();
}
if (ch === '"') {
return parseString();
}
if (ch === "t" || ch === "f") {
return parseBoolean();
}
if (ch === "n") {
return parseNull();
}
if (ch === "-" || isDigit(ch)) {
return parseNumber();
}
throw new Error("Unexpected character: " + ch);
}
function skipWhitespace() {
while (index < text.length && /\s/.test(text[index])) {
index++;
}
}
function parseObject() {
if (text[index] !== "{") {
throw new Error("Expected {");
}
index++;
const obj = {};
skipWhitespace();
if (text[index] === "}") {
index++;
return obj;
}
while (true) {
skipWhitespace();
if (text[index] !== '"') {
throw new Error("Expected property name");
}
const key = parseString();
skipWhitespace();
if (text[index] !== ":") {
throw new Error("Expected :");
}
index++;
const value = parseValue();
obj[key] = value;
skipWhitespace();
if (text[index] === "}") {
index++;
return obj;
}
if (text[index] !== ",") {
throw new Error("Expected , or }");
}
index++;
}
}
function parseArray() {
if (text[index] !== "[") {
throw new Error("Expected [");
}
index++;
const arr = [];
skipWhitespace();
if (text[index] === "]") {
index++;
return arr;
}
while (true) {
const value = parseValue();
arr.push(value);
skipWhitespace();
if (text[index] === "]") {
index++;
return arr;
}
if (text[index] !== ",") {
throw new Error("Expected , or ]");
}
index++;
}
}
function parseString() {
if (text[index] !== '"') {
throw new Error('Expected "');
}
index++;
let str = "";
while (index < text.length && text[index] !== '"') {
if (text[index] === "\\") {
index++;
if (index >= text.length) {
throw new Error("Unexpected end of string");
}
const escaped = text[index];
switch (escaped) {
case '"':
str += '"';
break;
case "\\":
str += "\\";
break;
case "/":
str += "/";
break;
case "n":
str += "\n";
break;
case "r":
str += "\r";
break;
case "t":
str += "\t";
break;
case "u":
if (index + 4 >= text.length) {
throw new Error("Invalid unicode escape");
}
const hex = text.substring(index + 1, index + 5);
if (!/^[0-9a-fA-F]{4}$/.test(hex)) {
throw new Error("Invalid unicode escape");
}
str += String.fromCharCode(parseInt(hex, 16));
index += 4;
break;
default:
str += escaped;
}
} else {
str += text[index];
}
index++;
}
if (index >= text.length) {
throw new Error("Unterminated string");
}
index++; // skip closing "
return str;
}
function parseNumber() {
let start = index;
if (text[index] === "-") {
index++;
}
if (text[index] === "0") {
index++;
} else if (isDigit(text[index])) {
while (index < text.length && isDigit(text[index])) {
index++;
}
} else {
throw new Error("Invalid number");
}
if (text[index] === ".") {
index++;
if (!isDigit(text[index])) {
throw new Error("Invalid number");
}
while (index < text.length && isDigit(text[index])) {
index++;
}
}
if (text[index] === "e" || text[index] === "E") {
index++;
if (text[index] === "+" || text[index] === "-") {
index++;
}
if (!isDigit(text[index])) {
throw new Error("Invalid number");
}
while (index < text.length && isDigit(text[index])) {
index++;
}
}
const numStr = text.substring(start, index);
const num = parseFloat(numStr);
if (isNaN(num)) {
throw new Error("Invalid number: " + numStr);
}
return num;
}
function parseBoolean() {
if (text.substring(index, index + 4) === "true") {
index += 4;
return true;
}
if (text.substring(index, index + 5) === "false") {
index += 5;
return false;
}
throw new Error("Expected boolean");
}
function parseNull() {
if (text.substring(index, index + 4) === "null") {
index += 4;
return null;
}
throw new Error("Expected null");
}
function isDigit(ch) {
return /[0-9]/.test(ch);
}
// 解析主值
let result;
try {
result = parseValue();
skipWhitespace();
if (index < text.length) {
throw new Error("Unexpected characters after parsing");
}
} catch (e) {
throw new SyntaxError("JSON.parse: " + e.message);
}
// 应用 reviver 转换
if (typeof reviver === "function") {
const walk = (holder, key, reviver) => {
const value = holder[key];
if (value && typeof value === "object") {
for (const k in value) {
if (Object.prototype.hasOwnProperty.call(value, k)) {
const nested = walk(value, k, reviver);
if (nested === undefined) {
delete value[k];
} else {
value[k] = nested;
}
}
}
}
return reviver.call(holder, key, value);
};
result = walk({ "": result }, "", reviver);
}
return result;
};使用示例
js
const jsonString = '{"name":"张三","age":18,"isStudent":false,"hobbies":["读书","运动"]}';
console.log(JSON.myParse(jsonString));
// { name: '张三', age: 18, isStudent: false, hobbies: ['读书', '运动'] }
// 使用 reviver
const parsed = JSON.myParse(jsonString, (key, value) => {
if (typeof value === "number") {
return value * 2;
}
return value;
});
console.log(parsed);
// { name: '张三', age: 36, isStudent: false, hobbies: ['读书', '运动'] }面试追问点
eval 方式的安全问题
js
// 恶意 JSON 字符串可能包含危险代码
const malicious = '{"name":"test"}; console.log("hacked")';
eval("(" + malicious + ")"); // 会执行额外代码reviver 函数的作用
用于在返回结果之前转换值,例如:
- 转换日期字符串为 Date 对象
- 过滤某些属性
- 数据脱敏
为什么用括号包裹
js
eval('({name: "test"})'); // 解析为对象字面量
eval('{name: "test"}'); // 解析为代码块,label 语法