fastjson源码分析-JSONToken

2021SC@SDUSC
上篇我们分析了MapSerializer和JavaBeanSerializer,这篇我们来分析token;

JSONToken成员:

com.alibaba.fastjson.parser.JSONToken定义了fastjson需要的token标识符:

  /** 1 关联到 error */
    public final static int ERROR                = 1;
    /** 2 关联到 int */
    public final static int LITERAL_INT          = 2;
    /** 3 关联到 float */
    public final static int LITERAL_FLOAT        = 3;
    /** 4 关联到 string */
    public final static int LITERAL_STRING       = 4;
    /** 5 关联到 iso8601 */
    public final static int LITERAL_ISO8601_DATE = 5;
    /** 6 关联到 true */
    public final static int TRUE                 = 6;
    /** 7 关联到 false */
    public final static int FALSE                = 7;
    /** 8 关联到 null */
    public final static int NULL                 = 8;
    /** 9 关联到 new */
    public final static int NEW                  = 9;
    /** 10 关联到 ( */
    public final static int LPAREN               = 10;
    /** 11 关联到 ) */
    public final static int RPAREN               = 11;
    /** 12 关联到 { */
    public final static int LBRACE               = 12;
    /** 13 关联到 } */
    public final static int RBRACE               = 13;
    /** 14 关联到 [ */
    public final static int LBRACKET             = 14;
    /** 15 关联到 ] */
    public final static int RBRACKET             = 15;
    /** 16 关联到 , */
    public final static int COMMA                = 16;
    /** 17 关联到 : */
    public final static int COLON                = 17;
    /** 18 关联到 ident */
    public final static int IDENTIFIER           = 18;
    /** 19 关联到 fieldName */
    public final static int FIELD_NAME           = 19;
    /** 20 关联到 EOF */
    public final static int EOF                  = 20;
    /** 21 关联到 Set */
    public final static int SET                  = 21;
    /** 22 关联到 TreeSet */
    public final static int TREE_SET             = 22;
    /** 23 关联到 undefined */
    public final static int UNDEFINED            = 23; // undefined
    /** 24 关联到 ; */
    public final static int SEMI                 = 24;
    /** 25 关联到 . */
    public final static int DOT                  = 25;
    /** 26 关联到 hex */
    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定义并实现了json串实现解析机制的基础,它对于反序列化很重要,我们先来理解它的含义:

/** 当前token含义 */
    protected int                            token;
    /** 记录当前扫描字符位置 */
    protected int                            pos;
    protected int                            features;

    /** 当前有效字符 */
    protected char                           ch;
    /** 流(或者json字符串)中当前的位置,每次读取字符会递增 */
    protected int                            bp;

    protected int                            eofPos;

    /** 字符缓冲区 */
    protected char[]                         sbuf;

    /** 字符缓冲区的索引,指向下一个可写
     *  字符的位置,也代表字符缓冲区字符数量
     */
    protected int                            sp;

    /**
     * number start position
     * 可以理解为 找到token时 token的首字符位置
     * 和bp不一样,这个不会递增,会在开始token前记录一次
     */
    protected int                            np;

JSONLexerBase成员函数
在开始分析词法分析实现过程中,我发现中解析存在大量重复代码实现或极其类似实现,重复代码主要解决类似c++内联调用,极其相似代码实现我会挑选有代表性的来说明(一般实现较为复杂),没有说明的成员函数可以参考代码注释

推断token类型

fastjson token类型推断当前json字符串是那种类型的token, 比如是字符串、花括号和逗号等等。

public final void nextToken() {
        /** 将字符buffer pos设置为初始0 */
        sp = 0;

        for (;;) {
            /** pos记录为流的当前位置 */
            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) {
                /** 读取单引号后面的字符串,和scanString逻辑一致 */
                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
                    /** 读取字符true */
                    scanTrue();
                    return;
                case 'f': // false
                    /** 读取字符false */
                    scanFalse();
                    return;
                case 'n': // new,null
                    /** 读取为new或者null的token */
                    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;
                        pos = bp = eofPos;
                    } else {
                        /** 忽略控制字符或者删除字符 */
                        if (ch <= 31 || ch == 127) {
                            next();
                            break;
                        }

                        lexError("illegal.char", String.valueOf((int) ch));
                        next();
                    }

                    return;
            }
        }

    }

跳过注释

解析注释主要分为两种,支持// 或者 /* */ 注释格式。

 protected void skipComment() {
        /** 读下一个字符 */
        next();
        /** 连续遇到左反斜杠/ */
        if (ch == '/') {
            for (;;) {
                /** 读下一个字符 */
                next();
                if (ch == '\n') {
                    /** 如果遇到换行符,继续读取下一个字符并返回 */
                    next();
                    return;
                    /** 如果已经遇到流结束,返回 */
                } else if (ch == EOI) {
                    return;
                }
            }
            /** 遇到`/*` 注释的格式 */
        } else if (ch == '*') {
            /** 读下一个字符 */
            next();
            for (; ch != EOI;) {
                if (ch == '*') {
                    /** 如果遇到*,继续尝试读取下一个字符,看看是否是/字符 */
                    next();
                    if (ch == '/') {
                        /** 如果确实是/字符,提前预读下一个有效字符后终止 */
                        next();
                        return;
                    } else {
                        /** 遇到非/ 继续跳过度下一个字符 */
                        continue;
                    }
                }
                /** 如果没有遇到`*\` 注释格式, 继续读下一个字符 */
                next();
            }
        } else {
            /** 不符合// 或者 \/* *\/ 注释格式 */
            throw new JSONException("invalid comment");
        }
    }

扫描字符串

当解析json字符串是"时,会调用扫描字符串方法。

 public final void scanString() {
        /** 记录当前流中token的开始位置, np指向引号的索引 */
        np = bp;
        hasSpecial = false;
        char ch;
        for (;;) {

            /** 读取当前字符串的字符 */
            ch = next();

            /** 如果遇到字符串结束符", 则结束 */
            if (ch == '\"') {
                break;
            }

            if (ch == EOI) {
                /** 如果遇到了结束符EOI,但是没有遇到流的结尾,添加EOI结束符 */
                if (!isEOF()) {
                    putChar((char) EOI);
                    continue;
                }
                throw new JSONException("unclosed string : " + ch);
            }

            /** 处理转译字符逻辑 */
            if (ch == '\\') {
                if (!hasSpecial) {
                    /** 第一次遇到\认为是特殊符号 */
                    hasSpecial = true;

                    /** 如果buffer空间不够,执行2倍扩容 */
                    if (sp >= sbuf.length) {
                        int newCapcity = sbuf.length * 2;
                        if (sp > newCapcity) {
                            newCapcity = sp;
                        }
                        char[] newsbuf = new char[newCapcity];
                        System.arraycopy(sbuf, 0, newsbuf, 0, sbuf.length);
                        sbuf = newsbuf;
                    }

                    /** 复制有效字符串到buffer中,不包括引号 */
                    copyTo(np + 1, sp, sbuf);
                    // text.getChars(np + 1, np + 1 + sp, sbuf, 0);
                    // System.arraycopy(buf, np + 1, sbuf, 0, sp);
                }

                /** 读取转译字符\下一个字符 */
                ch = next();

                /** 转换ascii字符,请参考:https://baike.baidu.com/item/ASCII/309296?fr=aladdin */
                switch (ch) {
                    case '0':
                        /** 空字符 */
                        putChar('\0');
                        break;
                    case '1':
                        /** 标题开始 */
                        putChar('\1');
                        break;
                    case '2':
                        /** 正文开始 */
                        putChar('\2');
                        break;
                    case '3':
                        /** 正文结束 */
                        putChar('\3');
                        break;
                    case '4':
                        /** 传输结束 */
                        putChar('\4');
                        break;
                    case '5':
                        /** 请求 */
                        putChar('\5');
                        break;
                    case '6':
                        /** 收到通知 */
                        putChar('\6');
                        break;
                    case '7':
                        /** 响铃 */
                        putChar('\7');
                        break;
                    case 'b': // 8
                        /** 退格 */
                        putChar('\b');
                        break;
                    case 't': // 9
                        /** 水平制表符 */
                        putChar('\t');
                        break;
                    case 'n': // 10
                        /** 换行键 */
                        putChar('\n');
                        break;
                    case 'v': // 11
                        /** 垂直制表符 */
                        putChar('\u000B');
                        break;
                    case 'f': // 12
                        /** 换页键 */
                    case 'F':
                        /** 换页键 */
                        putChar('\f');
                        break;
                    case 'r': // 13
                        /** 回车键 */
                        putChar('\r');
                        break;
                    case '"': // 34
                        /** 双引号 */
                        putChar('"');
                        break;
                    case '\'': // 39
                        /** 闭单引号 */
                        putChar('\'');
                        break;
                    case '/': // 47
                        /** 斜杠 */
                        putChar('/');
                        break;
                    case '\\': // 92
                        /** 反斜杠 */
                        putChar('\\');
                        break;
                    case 'x':
                        /** 小写字母x, 标识一个字符 */
                        char x1 = ch = next();
                        char x2 = ch = next();

                        /** x1 左移4位 + x2 */
                        int x_val = digits[x1] * 16 + digits[x2];
                        char x_char = (char) x_val;
                        putChar(x_char);
                        break;
                    case 'u':
                        /** 小写字母u, 标识一个字符 */
                        char u1 = ch = next();
                        char u2 = ch = next();
                        char u3 = ch = next();
                        char u4 = ch = next();
                        int val = Integer.parseInt(new String(new char[] { u1, u2, u3, u4 }), 16);
                        putChar((char) val);
                        break;
                    default:
                        this.ch = ch;
                        throw new JSONException("unclosed string : " + ch);
                }
                continue;
            }

            /** 没有转译字符,递增buffer字符位置 */
            if (!hasSpecial) {
                sp++;
                continue;
            }

            /** 继续读取转译字符后面的字符 */
            if (sp == sbuf.length) {
                putChar(ch);
            } else {
                sbuf[sp++] = ch;
            }
        }

        token = JSONToken.LITERAL_STRING;
        /** 自动预读下一个字符 */
        this.ch = next();
    }

在解析到字符串的时候会自动写入buffer

扫描数字类型

public final void scanNumber() {
        /** 记录当前流中token的开始位置, np指向数字字符索引 */
        np = bp;

        /** 兼容处理负数 */
        if (ch == '-') {
            sp++;
            next();
        }

        for (;;) {
            if (ch >= '0' && ch <= '9') {
                /** 如果是数字字符,递增索引位置 */
                sp++;
            } else {
                break;
            }
            next();
        }

        boolean isDouble = false;

        /** 如果遇到小数点字符 */
        if (ch == '.') {
            sp++;
            /** 继续读小数点后面字符 */
            next();
            isDouble = true;

            for (;;) {
                if (ch >= '0' && ch <= '9') {
                    sp++;
                } else {
                    break;
                }
                next();
            }
        }

        /** 继续读取数字后面的类型 */
        if (ch == 'L') {
            sp++;
            next();
        } else if (ch == 'S') {
            sp++;
            next();
        } else if (ch == 'B') {
            sp++;
            next();
        } else if (ch == 'F') {
            sp++;
            next();
            isDouble = true;
        } else if (ch == 'D') {
            sp++;
            next();
            isDouble = true;
        } else if (ch == 'e' || ch == 'E') {

            /** 扫描科学计数法 */
            sp++;
            next();

            if (ch == '+' || ch == '-') {
                sp++;
                next();
            }

            for (;;) {
                if (ch >= '0' && ch <= '9') {
                    sp++;
                } else {
                    break;
                }
                next();
            }

            if (ch == 'D' || ch == 'F') {
                sp++;
                next();
            }

            isDouble = true;
        }

        if (isDouble) {
            token = JSONToken.LITERAL_FLOAT;
        } else {
            token = JSONToken.LITERAL_INT;
        }
    }

扫描Boolean

 public final void scanTrue() {
        if (ch != 't') {
            throw new JSONException("error parse true");
        }
        next();

        if (ch != 'r') {
            throw new JSONException("error parse true");
        }
        next();

        if (ch != 'u') {
            throw new JSONException("error parse true");
        }
        next();

        if (ch != 'e') {
            throw new JSONException("error parse true");
        }
        next();

        if (ch == ' ' || ch == ',' || ch == '}' || ch == ']' || ch == '\n' || ch == '\r' || ch == '\t' || ch == EOI
                || ch == '\f' || ch == '\b' || ch == ':' || ch == '/') {
            /** 兼容性防御,标记是true的token */
            token = JSONToken.TRUE;
        } else {
            throw new JSONException("scan true error");
        }
    }

扫描标识符

public final void scanIdent() {
        /** 记录当前流中token的开始位置, np指向当前token前一个字符 */
        np = bp - 1;
        hasSpecial = false;

        for (;;) {
            sp++;

            next();
            /** 如果是字母或数字,继续读取 */
            if (Character.isLetterOrDigit(ch)) {
                continue;
            }

            /** 获取字符串值 */
            String ident = stringVal();

            if ("null".equalsIgnoreCase(ident)) {
                token = JSONToken.NULL;
            } else if ("new".equals(ident)) {
                token = JSONToken.NEW;
            } else if ("true".equals(ident)) {
                token = JSONToken.TRUE;
            } else if ("false".equals(ident)) {
                token = JSONToken.FALSE;
            } else if ("undefined".equals(ident)) {
                token = JSONToken.UNDEFINED;
            } else if ("Set".equals(ident)) {
                token = JSONToken.SET;
            } else if ("TreeSet".equals(ident)) {
                token = JSONToken.TREE_SET;
            } else {
                token = JSONToken.IDENTIFIER;
            }
            return;
        }
    }

扫描十六进制数

 public final void scanHex() {
        if (ch != 'x') {
            throw new JSONException("illegal state. " + ch);
        }
        next();
        /** 十六进制x紧跟着单引号 */
        /** @see com.alibaba.fastjson.serializer.SerializeWriter#writeHex(byte[]) */
        if (ch != '\'') {
            throw new JSONException("illegal state. " + ch);
        }

        np = bp;
        /** 这里一次next, for循环也读一次next, 因为十六进制被写成2个字节的单字符 */
        next();

        for (int i = 0;;++i) {
            char ch = next();
            if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'F')) {
                sp++;
                continue;
            } else if (ch == '\'') {
                sp++;
                /** 遇到结束符号,自动预读下一个字符 */
                next();
                break;
            } else {
                throw new JSONException("illegal state. " + ch);
            }
        }
        token = JSONToken.HEX;
    }
上一篇:SpringBoot整合 fastjson


下一篇:多个@JsonProperty操作同一个属性