2021SC@SDUSC
深度解析Fastjson中的Token
- Token定义
- Token类型解析
- JSONLexerBase成员变量
- JSONLexerBase解析方法
- 总结
Token定义
Token是Fastjson中定义的json字符串的同类型字段,即"{"、"["、数字、字符串等,用于分隔json字符串不同字段。
例如,{“姓名”:“张三”,“年龄”:“20”}是一个json字符串,在反序列化之前,需要先将其解析为{
、 姓名
、 :
、 张三
、 ,
、 年龄
、 :
、 20
、 }
这些字段的Token流,随后再根据class反序列化为响应的对象。
在进行Token解析之前,json字符串对程序而言只是一个无意义的字符串。需要将json字符串解析为一个个的Token,并以Token为单位解读json数据。
在package com.alibaba.fastjson.parser
包中,给出了所有Token的定义。
public class JSONToken {
//
public final static int ERROR = 1;
//
public final static int LITERAL_INT = 2;
//
public final static int LITERAL_FLOAT = 3;
//
public final static int LITERAL_STRING = 4;
//
public final static int LITERAL_ISO8601_DATE = 5;
public final static int TRUE = 6;
//
public final static int FALSE = 7;
//
public final static int NULL = 8;
//
public final static int NEW = 9;
//
public final static int LPAREN = 10; // ("("),
//
public final static int RPAREN = 11; // (")"),
//
public final static int LBRACE = 12; // ("{"),
//
public final static int RBRACE = 13; // ("}"),
//
public final static int LBRACKET = 14; // ("["),
//
public final static int RBRACKET = 15; // ("]"),
//
public final static int COMMA = 16; // (","),
//
public final static int COLON = 17; // (":"),
//
public final static int IDENTIFIER = 18;
//
public final static int FIELD_NAME = 19;
public final static int EOF = 20;
public final static int SET = 21;
public final static int TREE_SET = 22;
public final static int UNDEFINED = 23; // undefined
public final static int SEMI = 24;
public final static int DOT = 25;
public final static int HEX = 26;
public static String name(int value) {
switch (value) {
case ERROR:
return "error";
case LITERAL_INT:
return "int";
case LITERAL_FLOAT:
return "float";
case LITERAL_STRING:
return "string";
case LITERAL_ISO8601_DATE:
return "iso8601";
case TRUE:
return "true";
case FALSE:
return "false";
case NULL:
return "null";
case NEW:
return "new";
case LPAREN:
return "(";
case RPAREN:
return ")";
case LBRACE:
return "{";
case RBRACE:
return "}";
case LBRACKET:
return "[";
case RBRACKET:
return "]";
case COMMA:
return ",";
case COLON:
return ":";
case SEMI:
return ";";
case DOT:
return ".";
case IDENTIFIER:
return "ident";
case FIELD_NAME:
return "fieldName";
case EOF:
return "EOF";
case SET:
return "Set";
case TREE_SET:
return "TreeSet";
case UNDEFINED:
return "undefined";
case HEX:
return "hex";
default:
return "Unknown";
}
}
}
Token类型解析
JSONLexerBase成员变量
JSONLexerBase类实现了JSONLexer接口,是一个json分词器,用于分析Token类型,提取Token数据,进行反序列化的前期准备工作。
先看一下JSONLexerBase的成员变量:
protected int token;
protected int pos;
protected int features;
protected char ch;
protected int bp;
protected int eofPos;
/**
* A character buffer for literals.
*/
protected char[] sbuf;
protected int sp;
/**
* number start position
*/
protected int np;
protected boolean hasSpecial;
protected Calendar calendar = null;
protected TimeZone timeZone = JSON.defaultTimeZone;
protected Locale locale = JSON.defaultLocale;
public int matchStat = UNKNOWN;
private final static ThreadLocal<char[]> SBUF_LOCAL = new ThreadLocal<char[]>();
protected String stringDefaultValue = null;
protected int nanos = 0;
其中几个关键变量的含义:token
:当前token类型,用int表示,各个值代表的含义已经在上文中给出定义。
pos
:当前扫描到的字符的位置
ch
:当前扫描到的字符
sbuf
:字符缓冲区
bp
:或者json字符串中当前的位置,每次读取字符会递增
sp
:字符缓冲区的索引,指向下一个可写字符的位置,也代表字符缓冲区字符数量
np
:token首字符的位置,每次找到新的token时更新
JSONLexerBase解析方法
JSONLexerBase类有大量的方法用于判断Token类型、扫描Token、获取Token名称以及定位Token等等。其中有大量的重复代码或者实现起来非常相似的代码,这里挑选几个关键性的方法来分析判断Token、提取Token的逻辑。
nextToken()方法用于推断当前Token类型,例如字符串、{、数字等。
public final void nextToken() {
sp = 0;
for (;;) {
pos = bp;
if (ch == '/') {
skipComment();
continue;
}
if (ch == '"') {
scanString();
return;
}
if (ch == ',') {
next();
token = COMMA;
return;
}
if (ch >= '0' && ch <= '9') {
scanNumber();
return;
}
if (ch == '-') {
scanNumber();
return;
}
switch (ch) {
case '\'':
if (!isEnabled(Feature.AllowSingleQuotes)) {
throw new JSONException("Feature.AllowSingleQuotes is false");
}
scanStringSingleQuote();
return;
case ' ':
case '\t':
case '\b':
case '\f':
case '\n':
case '\r':
next();
break;
case 't': // true
scanTrue();
return;
case 'f': // false
scanFalse();
return;
case 'n': // new,null
scanNullOrNew();
return;
case 'T':
case 'N': // NULL
case 'S':
case 'u': // undefined
scanIdent();
return;
case '(':
next();
token = LPAREN;
return;
case ')':
next();
token = RPAREN;
return;
case '[':
next();
token = LBRACKET;
return;
case ']':
next();
token = RBRACKET;
return;
case '{':
next();
token = LBRACE;
return;
case '}':
next();
token = RBRACE;
return;
case ':':
next();
token = COLON;
return;
case ';':
next();
token = SEMI;
return;
case '.':
next();
token = DOT;
return;
case '+':
next();
scanNumber();
return;
case 'x':
scanHex();
return;
default:
if (isEOF()) { // JLS
if (token == EOF) {
throw new JSONException("EOF error");
}
token = EOF;
eofPos = pos = bp;
} else {
if (ch <= 31 || ch == 127) {
next();
break;
}
lexError("illegal.char", String.valueOf((int) ch));
next();
}
return;
}
}
}
可以看到,根据json字符串Token的多样性,该方法分了很多种情况来对不同类型的Token做出不同的响应。这段代码理解起来非常容易,纯粹的分类讨论,采用机械化的方法将所有情况归纳整合。这个方法是该类其他提取Token的方法的前提,每次提取Token之前,需要改方法判断Token类型。
下面挑选几个经典情况,说明该方法的执行流程:
- /:注释文本,调用skipComment()跳过
- ":字符串,调用scanString()扫描
- ,:逗号分隔符,调用next()方法跳过,判断下一个Token
- ch >= ‘0’ && ch <= ‘9’:数字,调用scanNumber扫描该数字串
- -:负号,判断接下来的内容为数字,仍调用scanNumber扫描数字串
- {、[、(…等各种左右括号,将token改为对应的数值,然后调用next()方法跳过
- 其他情况…
可以明确的是,字符串、数字等为有效信息,其余的大部分Token仅做分隔之用,所以都调用了next()方法跳过。
下面分析一下Int类型的Token的处理方法:
public int scanInt(char expectNext) {
matchStat = UNKNOWN;
int offset = 0;
char chLocal = charAt(bp + (offset++));
final boolean quote = chLocal == '"';
if (quote) {
chLocal = charAt(bp + (offset++));
}
final boolean negative = chLocal == '-';
if (negative) {
chLocal = charAt(bp + (offset++));
}
int value;
if (chLocal >= '0' && chLocal <= '9') {
value = chLocal - '0';
for (;;) {
chLocal = charAt(bp + (offset++));
if (chLocal >= '0' && chLocal <= '9') {
value = value * 10 + (chLocal - '0');
} else if (chLocal == '.') {
matchStat = NOT_MATCH;
return 0;
} else {
break;
}
}
if (value < 0) {
matchStat = NOT_MATCH;
return 0;
}
} else if (chLocal == 'n' && charAt(bp + offset) == 'u' && charAt(bp + offset + 1) == 'l' && charAt(bp + offset + 2) == 'l') {
matchStat = VALUE_NULL;
value = 0;
offset += 3;
chLocal = charAt(bp + offset++);
if (quote && chLocal == '"') {
chLocal = charAt(bp + offset++);
}
for (;;) {
if (chLocal == ',') {
bp += offset;
this.ch = charAt(bp);
matchStat = VALUE_NULL;
token = JSONToken.COMMA;
return value;
} else if (chLocal == ']') {
bp += offset;
this.ch = charAt(bp);
matchStat = VALUE_NULL;
token = JSONToken.RBRACKET;
return value;
} else if (isWhitespace(chLocal)) {
chLocal = charAt(bp + offset++);
continue;
}
break;
}
matchStat = NOT_MATCH;
return 0;
} else {
matchStat = NOT_MATCH;
return 0;
}
for (;;) {
if (chLocal == expectNext) {
bp += offset;
this.ch = this.charAt(bp);
matchStat = VALUE;
token = JSONToken.COMMA;
return negative ? -value : value;
} else {
if (isWhitespace(chLocal)) {
chLocal = charAt(bp + (offset++));
continue;
}
matchStat = NOT_MATCH;
return negative ? -value : value;
}
}
}
概括起来,该方法的执行步骤为:
- 取整数第一个字符判断是否是引号
- 如果是引号,取第一个数字字符
- 如果是负数,取下一个字符
- 如果是数字,循环将字符转换成数字
- 如果是null,读取null后面的一个字符,忽略空白字符
通过这样的逻辑,扫描Int类型的Token并提取,用于下一步的反序列化
总结
要进行json字符串的反序列化,首先要将json字符串Token化,取出每一段由数字、字符串、花括号、小括号、冒号等构成的Token并从中提取有效信息。在获取Token之前,json字符串与普通字符串没有区别,均无法由程序识别并进行转换。将json字符串的所有包含有效信息的Token提取出来,再获取转换对象的class,最后才能进行关键的反序列化。