在 json.org/json-zh.htm… 中介绍了JSON各类语法的状态机。本文根据这些状态机给出解析JSON的typescript实现方法。
JSON 是一种非常简单的语言。有以下几个特点:
null[
parseValueparseObjectparseArrayparseNumberparseStringskipWhitespacepospeek=s[pos]Parse
class Parse {
private pos = 0;
private get peek() {
return this.s[this.pos];
}
constructor(private s: string) {}
}
Deno.test
skipWhitespace: 跳过空白符
spacelinefeedcarriage feedhorizontal tab
class Parse {
skipWhitespace() {
while (Parse.isWhitespace(this.peek)) {
this.pos++;
}
}
static isWhitespace(char: string) {
switch (char.charCodeAt(0)) {
case 32: // 空格 space
case 10: // 换行 linefeed
case 13: // 回车 carriage feed
case 9: // 水平符号 horizontal tab
return true;
default:
return false;
}
}
}
Deno.test("isWhitespace", () => {
assertEquals(Parse.isWhitespace(" "), true);
assertEquals(
Parse.isWhitespace(`
`),
true,
);
assertEquals(Parse.isWhitespace(" "), true);
assertEquals(Parse.isWhitespace("12"), false);
});
parseValue: 解析一个值
[{-"tnftruenullfalse
class Parse {
parseValue() {
this.skipWhitespace();
switch (this.peek) {
case "n":
this.pos += 4;
return null;
case "t":
this.pos += 4;
return true;
case "f":
this.pos += 5;
return false;
case "{":
return this.parseObject();
case "[":
return this.parseArray();
case '"':
return this.parseString();
case "-":
this.pos++;
return -this.parseNumber();
case "0":
case "1":
case "2":
case "3":
case "4":
case "5":
case "6":
case "7":
case "8":
case "9":
return this.parseNumber();
default:
throw "解析错误";
}
}
}
Deno.test("测试基础类型", () => {
assertEquals(new Parse("true").parseValue(), true);
assertEquals(new Parse("false").parseValue(), false);
assertEquals(new Parse('"123"').parseValue(), "123");
assertEquals(new Parse("1").parseValue(), 1);
assertEquals(new Parse("1.2").parseValue(), 1.2);
});
parseArray: 解析一个数组
上图是数组类型状态机。对应的代码如下。
class Parse {
parseArray() {
this.pos++; // 跳过 '['
this.skipWhitespace();
const list: unknown[] = [];
if (this.peek == "]") {
this.pos++;
return list;
}
while (true) {
this.skipWhitespace();
list.push(this.parseValue());
this.skipWhitespace();
if (this.peek == "]") {
this.pos++;
break;
}
if (this.peek == ",") {
this.pos++;
}
}
return list;
}
}
Deno.test("测试数组", () => {
assertEquals(new Parse("[1,2]").parseArray(), [1, 2]);
assertEquals(new Parse("[1,[2,3]]").parseArray(), [1, [2, 3]]);
assertEquals(new Parse("[1,[2,3,[1]]]").parseArray(), [1, [2, 3, [1]]]);
assertEquals(new Parse("[1,[2,3,[1],4,[5]]]").parseArray(), [1, [
2,
3,
[1],
4,
[5],
]]);
});
parseObject: 解析一个对象
上图是对象类型状态机。对应的代码如下。
class Parse {
parseObject() {
this.pos++; // skip '{'
this.skipWhitespace();
const values: any = {};
if (this.peek == "}") {
this.pos++;
return values;
}
while (true) {
this.skipWhitespace();
const key = this.parseString();
this.skipWhitespace();
if (this.peek != ":") {
throw "FormatException";
}
this.pos++;
this.skipWhitespace();
const value = this.parseValue();
this.skipWhitespace();
values[key] = value;
// @ts-ignore
if (this.peek === "}") {
this.pos++;
break;
}
// @ts-ignore
if (this.peek === ",") {
this.pos++;
}
}
return values;
}
}
parseString: 解析一个字符串
上图是字符串对应的状态机。为了简单表达,下面的代码没有解析特殊字符。如果读者感兴趣,可以自己实现。
class Parse {
parseString(): string {
this.pos++; //skip '"'
if (this.peek == '"') {
this.pos++;
return "";
}
var str = "";
while (true) {
if (this.peek == '"') {
break;
}
str += this.peek;
this.pos++;
}
this.pos++; //skip '"'
return str;
}
}
parseNumber: 解析一个数字
上图是数字对应的状态机。为了简单表达,下面的代码只解析了简单的整数和小数。如果读者感兴趣,可以自己实现。
class Parse {
parseNumber() {
let num = "";
while (Parse.isNumber(this.peek)) {
num += this.peek;
this.pos++;
}
if (this.peek === ".") {
this.pos++;
const decimal = this.parseNumber();
num += "." + decimal;
}
return parseFloat(num);
}
static isNumber(char: string) {
return char >= "0" && char <= "9";
}
}
parse: 解析一个JSON字符串
class Parse {
parse() {
this.skipWhitespace();
let json: any = this.s;
switch (this.peek) {
case "{":
json = this.parseObject();
break;
case "[":
json = this.parseArray();
break;
default:
json = this.parseValue();
}
this.skipWhitespace();
return json;
}
}
Deno.test("测试完整的一个JSON", () => {
const obj = {
"level": 2,
"tid": 73753,
"classId": 82,
"gameType": 6,
"uids": [
103136,
100113,
100778,
],
"info": [
{
"uid": 103136,
"winChips": 455700,
"userChips": 100372300,
"fee": -135000,
"blind": 300000,
},
{
"uid": 100113,
"winChips": -300000,
"userChips": 93139748,
"fee": 0,
"blind": 300.45,
},
{
"uid": 100778,
"winChips": -300000,
"userChips": 99100000,
"fee": 0,
"blind": 300000,
},
],
};
assertEquals(new Parse(JSON.stringify(obj)).parseObject(), obj);
});