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类型。
下面挑选几个经典情况,说明该方法的执行流程:

  1. /:注释文本,调用skipComment()跳过
  2. ":字符串,调用scanString()扫描
  3. ,:逗号分隔符,调用next()方法跳过,判断下一个Token
  4. ch >= ‘0’ && ch <= ‘9’:数字,调用scanNumber扫描该数字串
  5. -:负号,判断接下来的内容为数字,仍调用scanNumber扫描数字串
  6. {、[、(…等各种左右括号,将token改为对应的数值,然后调用next()方法跳过
  7. 其他情况…

可以明确的是,字符串、数字等为有效信息,其余的大部分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;
            }
        }
    }

概括起来,该方法的执行步骤为:

  1. 取整数第一个字符判断是否是引号
  2. 如果是引号,取第一个数字字符
  3. 如果是负数,取下一个字符
  4. 如果是数字,循环将字符转换成数字
  5. 如果是null,读取null后面的一个字符,忽略空白字符

通过这样的逻辑,扫描Int类型的Token并提取,用于下一步的反序列化

总结

要进行json字符串的反序列化,首先要将json字符串Token化,取出每一段由数字、字符串、花括号、小括号、冒号等构成的Token并从中提取有效信息。在获取Token之前,json字符串与普通字符串没有区别,均无法由程序识别并进行转换。将json字符串的所有包含有效信息的Token提取出来,再获取转换对象的class,最后才能进行关键的反序列化。