JSON 验证器工作原理 - 从词法分析到语法树(2026 技术解析)

1935 字
10 分钟
JSON 验证器工作原理 - 从词法分析到语法树(2026 技术解析)

🔬 JSON 验证器是如何工作的?#

当你将 JSON 数据粘贴到验证工具中时,背后发生了什么?本文将深入解析 JSON 验证器的内部工作原理。

graph LR A[原始 JSON 文本] --> B[词法分析] B --> C[Token 流] C --> D[语法分析] D --> E[语法树] E --> F[语义验证] F --> G[错误报告]

📚 编译原理基础#

编译器的工作流程#

源代码 → 词法分析 → 语法分析 → 语义分析 → 代码生成
↓ ↓ ↓
Token 流 AST 中间代码

JSON 验证器的工作流程与编译器类似,但更加简化。


🔍 第一阶段:词法分析(Lexical Analysis)#

什么是词法分析?#

词法分析(Lexical Analysis)是将字符流转换为 Token 流的过程。

输入:

{"name": "John", "age": 30}

输出:

Token 1: L_BRACE {
Token 2: STRING "name"
Token 3: COLON :
Token 4: STRING "John"
Token 5: COMMA ,
Token 6: STRING "age"
Token 7: COLON :
Token 8: NUMBER 30
Token 9: R_BRACE }

词法分析器实现#

简化版词法分析器:

class Token {
constructor(type, value, position) {
this.type = type;
this.value = value;
this.position = position;
}
}
class Lexer {
constructor(text) {
this.text = text;
this.pos = 0;
this.currentChar = this.text[0];
this.tokens = [];
}
error(message) {
throw new Error(`词法错误:${message} 在位置 ${this.pos}`);
}
advance() {
this.pos++;
this.currentChar = this.text[this.pos] || null;
}
skipWhitespace() {
while (this.currentChar && /\s/.test(this.currentChar)) {
this.advance();
}
}
readString() {
const start = this.pos;
this.advance(); // 跳过开头的 "
let result = '';
while (this.currentChar && this.currentChar !== '"') {
if (this.currentChar === '\\') {
this.advance();
if (this.currentChar) {
result += this.currentChar;
this.advance();
}
} else {
result += this.currentChar;
this.advance();
}
}
if (!this.currentChar) {
this.error('未闭合的字符串');
}
this.advance(); // 跳过结尾的 "
return new Token('STRING', result, start);
}
readNumber() {
const start = this.pos;
let result = '';
while (this.currentChar && /[\d.\-eE+]/.test(this.currentChar)) {
result += this.currentChar;
this.advance();
}
return new Token('NUMBER', parseFloat(result), start);
}
getNextToken() {
// 跳过空白字符
this.skipWhitespace();
if (!this.currentChar) {
return new Token('EOF', null, this.pos);
}
const start = this.pos;
// 识别各种 Token
switch (this.currentChar) {
case '{':
this.advance();
return new Token('L_BRACE', '{', start);
case '}':
this.advance();
return new Token('R_BRACE', '}', start);
case '[':
this.advance();
return new Token('L_BRACKET', '[', start);
case ']':
this.advance();
return new Token('R_BRACKET', ']', start);
case ':':
this.advance();
return new Token('COLON', ':', start);
case ',':
this.advance();
return new Token('COMMA', ',', start);
case '"':
return this.readString();
default:
if (/[\d\-]/.test(this.currentChar)) {
return this.readNumber();
}
if (/[a-zA-Z]/.test(this.currentChar)) {
return this.readKeyword();
}
this.error(`未知字符:${this.currentChar}`);
}
}
readKeyword() {
const start = this.pos;
let result = '';
while (this.currentChar && /[a-zA-Z]/.test(this.currentChar)) {
result += this.currentChar;
this.advance();
}
// 识别 true, false, null
if (result === 'true' || result === 'false') {
return new Token('BOOLEAN', result, start);
}
if (result === 'null') {
return new Token('NULL', result, start);
}
this.error(`未知关键字:${result}`);
}
tokenize() {
while (true) {
const token = this.getNextToken();
this.tokens.push(token);
if (token.type === 'EOF') break;
}
return this.tokens;
}
}
// 使用示例
const lexer = new Lexer('{"name": "John"}');
const tokens = lexer.tokenize();
console.log(tokens);

🌳 第二阶段:语法分析(Syntax Analysis)#

什么是语法分析?#

语法分析(Syntax Analysis)是将 Token 流转换为抽象语法树(AST)的过程。

JSON 的语法规则(简化版):

json → object | array
object → L_BRACE (STRING COLON json (COMMA STRING COLON json)*)? R_BRACE
array → L_BRACKET (json (COMMA json)*)? R_BRACKET

递归下降分析器#

语法分析器实现:

class Parser {
constructor(tokens) {
this.tokens = tokens;
this.pos = 0;
this.currentToken = this.tokens[0];
}
error(message) {
const token = this.currentToken;
throw new Error(
`语法错误:${message} 在第 ${token.position} 行`
);
}
advance() {
this.pos++;
this.currentToken = this.tokens[this.pos] ||
new Token('EOF', null, this.pos);
}
expect(type) {
if (this.currentToken.type !== type) {
this.error(`期望 ${type},但得到 ${this.currentToken.type}`);
}
this.advance();
}
parse() {
if (this.currentToken.type === 'L_BRACE') {
return this.parseObject();
}
if (this.currentToken.type === 'L_BRACKET') {
return this.parseArray();
}
this.error('JSON 必须以 { 或 [ 开始');
}
parseObject() {
const obj = {};
const startToken = this.currentToken;
this.expect('L_BRACE');
// 空对象
if (this.currentToken.type === 'R_BRACE') {
this.advance();
return obj;
}
// 解析键值对
while (true) {
// 解析键
if (this.currentToken.type !== 'STRING') {
this.error('对象的键必须是字符串');
}
const key = this.currentToken.value;
this.advance();
// 解析冒号
this.expect('COLON');
// 解析值
const value = this.parseValue();
obj[key] = value;
// 检查逗号或结束
if (this.currentToken.type === 'COMMA') {
this.advance();
} else if (this.currentToken.type === 'R_BRACE') {
this.advance();
break;
} else {
this.error('期望 , 或 }');
}
}
return obj;
}
parseArray() {
const arr = [];
this.expect('L_BRACKET');
// 空数组
if (this.currentToken.type === 'R_BRACKET') {
this.advance();
return arr;
}
// 解析元素
while (true) {
const value = this.parseValue();
arr.push(value);
// 检查逗号或结束
if (this.currentToken.type === 'COMMA') {
this.advance();
} else if (this.currentToken.type === 'R_BRACKET') {
this.advance();
break;
} else {
this.error('期望 , 或 ]');
}
}
return arr;
}
parseValue() {
const token = this.currentToken;
switch (token.type) {
case 'L_BRACE':
return this.parseObject();
case 'L_BRACKET':
return this.parseArray();
case 'STRING':
this.advance();
return token.value;
case 'NUMBER':
this.advance();
return token.value;
case 'BOOLEAN':
this.advance();
return token.value === 'true';
case 'NULL':
this.advance();
return null;
default:
this.error(`意外的 Token: ${token.type}`);
}
}
}
// 使用示例
const lexer = new Lexer('{"name": "John", "age": 30}');
const tokens = lexer.tokenize();
const parser = new Parser(tokens);
const ast = parser.parse();
console.log(ast);
// 输出:{ name: 'John', age: 30 }

⚠️ 第三阶段:错误检测#

常见错误检测#

1. 缺少逗号检测:

parseObject() {
// ...
while (true) {
const key = this.currentToken.value;
this.advance();
// 如果没有冒号,可能是缺少逗号
if (this.currentToken.type !== 'COLON') {
if (this.currentToken.type === 'STRING') {
this.error('缺少逗号分隔符');
}
}
// ...
}
}

2. 尾随逗号检测:

parseArray() {
// ...
while (true) {
const value = this.parseValue();
arr.push(value);
if (this.currentToken.type === 'COMMA') {
this.advance();
// 检查是否是最后一个元素
if (this.currentToken.type === 'R_BRACKET') {
this.error('JSON 不允许尾随逗号');
}
} else if (this.currentToken.type === 'R_BRACKET') {
this.advance();
break;
}
}
}

3. 引号不匹配检测:

readString() {
const start = this.pos;
this.advance(); // 跳过开头的 "
let result = '';
while (this.currentChar && this.currentChar !== '"') {
if (this.currentChar === '\\') {
this.advance();
if (this.currentChar) {
result += this.currentChar;
this.advance();
}
} else {
result += this.currentChar;
this.advance();
}
}
// 检查是否找到结束的引号
if (!this.currentChar) {
this.error('未闭合的字符串,缺少结束引号');
}
this.advance(); // 跳过结尾的 "
return new Token('STRING', result, start);
}

🌲 第四阶段:构建语法树#

抽象语法树(AST)结构#

示例 JSON:

{
"name": "John",
"age": 30,
"skills": ["js", "python"]
}

对应的 AST:

ObjectExpression
├── Property: "name"
│ └── StringLiteral: "John"
├── Property: "age"
│ └── NumberLiteral: 30
└── Property: "skills"
└── ArrayExpression
├── StringLiteral: "js"
└── StringLiteral: "python"

AST 节点类型#

// AST 节点类型定义
const NodeTypes = {
OBJECT: 'ObjectExpression',
ARRAY: 'ArrayExpression',
PROPERTY: 'Property',
STRING: 'StringLiteral',
NUMBER: 'NumberLiteral',
BOOLEAN: 'BooleanLiteral',
NULL: 'NullLiteral'
};
// 增强的解析器,构建完整 AST
class ASTBuilder extends Parser {
parseObject() {
const node = {
type: NodeTypes.OBJECT,
properties: [],
position: this.currentToken.position
};
this.expect('L_BRACE');
if (this.currentToken.type !== 'R_BRACE') {
do {
const prop = this.parseProperty();
node.properties.push(prop);
if (this.currentToken.type === 'COMMA') {
this.advance();
} else {
break;
}
} while (true);
}
this.expect('R_BRACE');
return node;
}
parseProperty() {
const key = this.currentToken.value;
this.advance();
this.expect('COLON');
const value = this.parseValue();
return {
type: NodeTypes.PROPERTY,
key: key,
value: value
};
}
}

🔧 实际验证器实现#

完整验证器#

class JSONValidator {
constructor() {
this.errors = [];
}
validate(text) {
this.errors = [];
try {
const lexer = new Lexer(text);
const tokens = lexer.tokenize();
const parser = new ASTBuilder(tokens);
const ast = parser.parse();
return {
valid: true,
ast: ast,
errors: []
};
} catch (error) {
this.errors.push({
message: error.message,
position: error.position || 0,
type: 'SyntaxError'
});
return {
valid: false,
ast: null,
errors: this.errors
};
}
}
validateWithDetails(text) {
const result = this.validate(text);
if (!result.valid) {
// 添加修复建议
result.errors.forEach(error => {
if (error.message.includes('缺少逗号')) {
error.suggestion = '在上一行末尾添加逗号';
} else if (error.message.includes('尾随逗号')) {
error.suggestion = '移除最后一个元素后的逗号';
} else if (error.message.includes('未闭合')) {
error.suggestion = '添加缺少的闭合符号';
}
});
}
return result;
}
}
// 使用示例
const validator = new JSONValidator();
const result = validator.validateWithDetails('{"name": "John" "age": 30}');
if (!result.valid) {
console.log('验证失败:');
result.errors.forEach(error => {
console.log(`- ${error.message}`);
if (error.suggestion) {
console.log(` 建议:${error.suggestion}`);
}
});
}

📊 性能优化#

1. 增量解析#

对于大型 JSON 文件,可以实现增量解析:

class IncrementalParser {
constructor() {
this.cache = new Map();
}
parseChunk(chunk, offset) {
const cacheKey = `${offset}-${chunk.length}`;
if (this.cache.has(cacheKey)) {
return this.cache.get(cacheKey);
}
const result = this.parse(chunk);
this.cache.set(cacheKey, result);
return result;
}
}

2. 错误恢复#

class ErrorRecoveryParser extends Parser {
parseWithErrorRecovery() {
const nodes = [];
try {
while (this.currentToken.type !== 'EOF') {
try {
const node = this.parse();
nodes.push(node);
} catch (error) {
this.errors.push(error);
this.recoverFromError();
}
}
} catch (fatalError) {
this.errors.push(fatalError);
}
return nodes;
}
recoverFromError() {
// 跳过错误的 Token,尝试恢复
while (this.currentToken.type !== 'EOF') {
if (['L_BRACE', 'R_BRACE', 'L_BRACKET', 'R_BRACKET'].includes(
this.currentToken.type
)) {
return; // 找到同步点
}
this.advance();
}
}
}

💬 总结#

JSON 验证器的工作原理包含四个关键阶段:

  1. ✅ 词法分析:字符流 → Token 流
  2. ✅ 语法分析:Token 流 → 语法树
  3. ✅ 错误检测:识别语法错误
  4. ✅ 构建 AST:生成抽象语法树

核心技术:

  • 正则表达式匹配 Token
  • 递归下降语法分析
  • 错误恢复机制
  • 增量解析优化

实际应用:

  • 在线 JSON 验证工具
  • 编辑器语法高亮
  • IDE 智能提示
  • CI/CD 自动校验

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

最后更新于 2026-03-29,距今已过 201 天

部分内容可能已过时

公告
🎉 已升级 2.0 版本,欢迎使用。
分类
标签
站点统计
文章
197
分类
24
标签
747
总字数
334,099
运行时长
0 天
最后活动
0 天前

目录