分析一下Json解析器原理

Z技术 2022年04月05日 1,146次浏览

1,使用地址:http://sita.site/json/index.html

2,效果

0f82a3adf0074a1495489d34d27442b9.png

3,源码 https://gitee.com/Sowe/json-parser.git

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Z技术-Json解析</title>
    <meta name="keywords" content="Z技术-Json解析">
    <meta name="description" content="Z技术-Json解析">
    <script>
        const TokenTypes = {
            OPEN_OBJECT: '{',
            CLOSE_OBJECT: '}',
            OPEN_ARRAY: '[',
            CLOSE_ARRAY: ']',
            KEY: 'key',
            STRING: 'string',
            NUMBER: 'number',
            TRUE: 'true',
            FALSE: 'false',
            NULL: 'null',
            COLON: ':',
            COMMA: ',',
            EOF: 'eof'
        };

        class Token {
            constructor(type, string) {
                this.type = type;
                this.string = string || this.type;
            }
        }

        class Lexer {
            constructor(json) {
                this._json = json;
                this._index = 0;
            }
            _isEnd() {
                return this._index > this._json.length;
            }
            _walk() {
                return this._json[this._index++];
            }
            _currentChat() {
                return this._json[this._index];
            }
            _nextChar() {
                return this._json[this._index + 1];
            }
            _readString() {
                let tmp = '';
                while (!this._isEnd()) {
                    const c = this._walk();
                    if (c == '"') break;
                    if (c == '\\' &amp;&amp; this._nextChar() == '"') {
                        this._walk();
                        tmp += '"';
                        continue;
                    }
                    tmp += c;
                }
                return new Token(TokenTypes.STRING, tmp);
            }
            _isLetter(c) {
                return c >= 'a' &amp;&amp; c <= 'z' || c >= 'A' &amp;&amp; c <= 'Z';
            }
            _isDigit(c) {
                if(c=="-")
                    return "-"
                return c >= '0' &amp;&amp; c <= '9';
            }
            _readDigit(c) {
                let tmp = c;
                while (!this._isEnd()) {
                    const c = this._currentChat();
                    if (!this._isDigit(c)) break;
                    this._walk();
                    tmp += c;
                }
                return new Token(TokenTypes.NUMBER, tmp);
            }
            _readWord(c) {
                let tmp = c;
                while (!this._isEnd()) {
                    const c = this._currentChat();
                    if (!this._isLetter(c)) break;
                    this._walk();
                    tmp += c;
                }
                return tmp;
            }
            nextToken() {
                const c = this._walk();
                if (this._isEnd()) return new Token(TokenTypes.EOF);
                switch (c) {
                    case ' ':
                        return this.nextToken();
                    case '{':
                        return new Token(TokenTypes.OPEN_OBJECT);
                    case '}':
                        return new Token(TokenTypes.CLOSE_OBJECT);
                    case '[':
                        return new Token(TokenTypes.OPEN_ARRAY);
                    case ']':
                        return new Token(TokenTypes.CLOSE_ARRAY);
                    case ':':
                        return new Token(TokenTypes.COLON);
                    case ',':
                        return new Token(TokenTypes.COMMA);
                    case '"':
                        return this._readString();
                }
                if (this._isDigit(c)) return this._readDigit(c);
                if (this._isLetter(c)) {
                    const word = this._readWord(c);
                    switch (word) {
                        case 'true':
                            return new Token(TokenTypes.TRUE);
                        case 'false':
                            return new Token(TokenTypes.FALSE);
                        case 'null':
                            return new Token(TokenTypes.NULL);
                    }
                    throw new Error(`expect true, false, null actual ${word}`);
                }
                throw new Error(`not suppor ${c}`);
            }
        }

        class Parser {
            constructor(json) {
                this._lexer = new Lexer(json);
                this._token = this._lexer.nextToken();
            }
            _matchToken(type) {
                const string = this._token.string;
                if (!this._isToken(type)) throw new Error(`expect ${type} actual ${this._token.type}`);
                this._walk();
                return string;
            }
            _isToken(type) {
                return this._token.type == type;
            }
            _currentString() {
                return this._token.string;
            }
            _walk() {
                this._token = this._lexer.nextToken();
            }
            parse() {
                return this._visitValue();
            }
            _visitValue() {
                if (this._isToken(TokenTypes.NUMBER)) {
                    const str = this._currentString();
                    this._walk();
                    return parseInt(str);
                }
                if (this._isToken(TokenTypes.TRUE)) {
                    this._walk();
                    return true;
                }
                if (this._isToken(TokenTypes.FALSE)) {
                    this._walk();
                    return false;
                }
                if (this._isToken(TokenTypes.STRING)) {
                    const str = this._currentString();
                    this._walk();
                    return str;
                }
                if (this._isToken(TokenTypes.NULL)) {
                    this._walk();
                    return null;
                }
                if (this._isToken(TokenTypes.OPEN_OBJECT)) {
                    this._walk();
                    const object = this._visitObject();
                    this._matchToken(TokenTypes.CLOSE_OBJECT);
                    return object;
                }
                if (this._isToken(TokenTypes.OPEN_ARRAY)) {
                    this._walk();
                    const array = this._visitArray();
                    this._matchToken(TokenTypes.CLOSE_ARRAY);
                    return array;
                }
            }
            _visitObject() {
                const object = {};
                while (true) {
                    const key = this._matchToken(TokenTypes.STRING);
                    this._matchToken(TokenTypes.COLON);
                    const value = this._visitValue();
                    object[key] = value;
                    if (!this._isToken(TokenTypes.COMMA)) break;
                    this._walk();
                }
                return object;
            }
            _visitArray() {
                const array = [];
                while (true) {
                    const value = this._visitValue();
                    array.push(value);
                    if (!this._isToken(TokenTypes.COMMA)) break;
                    this._walk()
                }
                return array;
            }
        }
    </script>
    <style>
        *{
            font-size: 1.1em;
            box-sizing: border-box;
        }
    </style>
</head>

<body>
    <div style="width: 1205px;height: 800px;margin: 1em auto;text-align: center;">
        <input onclick="cal()" type="button" value="点击解析" style="width: inherit;" />
        <div style="width: 600px;height: 800px;border: 1px solid green;float: left;">
            <textarea id="left" onkeydown="cal()"
                style="width: 600px;height: 800px;">[{"a": false, "b": true, "c": "string", "d": null, "e": 12},{"name":[1,2,3]},{"abc":[{"name":"123","jj":"124124"},{"name":"123"}]}]
            </textarea>
        </div>
        <div style="width: 600px;height: 800px;border: 1px solid green;float: left;">
            <textarea id="right" style="width: 600px;height: 800px;" readonly></textarea>
        </div>
    </div>
    <div style="text-align: center;margin-top: 2em;font-size: 1em;">查看博客:<a style="text-decoration:none" href="http://sita.site">Z技术</a></div>
</body>
<script>
    function cal() {
        try {
            var source = document.getElementById('left').value.trim().replace(/\\/g,'');
            // console.log(source);
            const parser = new Parser(source);
            const result = parser.parse();
            document.getElementById("right").innerHTML = JSON.stringify(result, null, 2);
        }
        catch(e) {
            document.getElementById("right").innerHTML="解析失败,请确认是否为json字符串 \n"+e;
        }
    }
    document.getElementById('left').onchange = cal();

</script>
</html>

 更多信息请关注公众号:
 20220401152838