一起来实现一个 “fast”json 序列化工具吧

2023年 9月 16日 57.9k 0

背景

最近在学习编译原理的过程中了解到有些语言的编译器前端是利用 antlr4 实现源代码到 AST 的。那么什么是 antlr4 呢? antlr4 本身是基于 Java 开发的语法分析器生成工具,他能够根据文法规则生成对应的语法分析器,广泛应用于 DSL 构建,语言词法语法解析等领域。基于这个特性我们可以编写一个简单的 json 解析工具,什么,你问我为什么不编写一个脚本语言?我不会呀_。

说明

文章开始之前让我先介绍一下 antlr4 的使用步骤吧:

  • 新建 g4 文件
    antlr4 使用一种名为 antlr 的语法描述语言来定义你的语法结构。你需要创建一个 .g4 文件来定义你的语法。这个文件描述了你希望 antlr4 解析的输入格式以及如何解析它。
  • 生成语法解析器
    一旦你创建了 .g4 文件,你就可以使用 antlr4 的工具来生成对应的语法解析器。
  • 遍历 AST
    antlr4 中我们可以利用 Visitor 方式和 Listener 方式去遍历 AST 树,针对每个节点做一些处理。不过对于实现一个 json 解析器,这不是必须的。

定义语法文件

定义语法文件有几点需要注意:

  • 文件名和 grammar 关键字后跟的语法名应该一致。
  • 文法规则和词法规则可以同时存在一个文件中,但文法以小写开头,词法以大写开头。
  • 在构建一个语法文件时,一般是先考虑文法,再考虑词法。就像是我们造句,先会考虑句子的结构,再往里面填词。但是实际语法分析的过程是:输入流先经过词法分析器生成匹配的词法符号流,词法符号流经过语法分析器生成匹配的语法结构。
    接下来让我们一起构建一个 json 的语法文件吧。
grammar JSON; 
// g4文件一般以grammar开头,并且与文件名同名

json
   : value EOF
   ;
// 定义了json的结束符及内容的语法


obj
   : '{' pair (',' pair)* '}'
   | '{' '}'
   ;
// 用obj表示json中key-vlaue形式的语法

pair
   : STRING ':' value
   ;
// 定义了json中key-value形式的语法

arr
   : '[' value (',' value)* ']'
   | '[' ']'
   ;
// 定义了json中数组的语法

value
   : STRING
   | NUMBER
   | obj
   | arr
   | 'true'
   | 'false'
   | 'null'
   ;
// 定义了json最基础的语法

STRING
   : '"' (ESC | SAFECODEPOINT)* '"'
   ;
// 定义一个STRING词法

fragment ESC
   : '\' (["\/bfnrt] | UNICODE)
   ;


fragment UNICODE
   : 'u' HEX HEX HEX HEX
   ;


fragment HEX
   : [0-9a-fA-F]
   ;


fragment SAFECODEPOINT
   : ~ ["\u0000-u001F]
   ;


NUMBER
   : '-'? INT ('.' [0-9] +)? EXP?
   ;
// 定义一个NUMBER词法

fragment INT
   : '0' | [1-9] [0-9]*
   ;
// | 表示分支选项,这里表示数字类型,支持'0'开头,fragment表示

fragment EXP
   : [Ee] [+-]? [0-9]+
   ;
 
WS
   : [ tnr] + -> skip
   ;
// + 表示 匹配前一个匹配项至少一次,skip表示跳过,本句的意思是匹配到空格、制表符、回车符或换行符则跳过

测试语法文件

这里我使用 antlr4-idea 插件进行调试语法文件,输入一个 json 序列,通过右边的 Parse tree 可以看到解析出来的抽象语法树。
42961d59642d45e9b9817f57558e1b76

生成语法解析器

语法解析器有多种生成方式,我们可以通过命令生成,也可以通过idea插件去生成,这里我演示一种使用 idea 插件生成的方式(如果你也想试试,需要先装一个 antlr4-idea 插件)。

  • 配置插件
    在g4文件中右键选择 Configure ANTLR... 按钮,进行一些简单的配置即可:
    4e213a9c62f141b1b098242af84e86d2

  • 生成目标文件
    我们可以在 g4 文件中右键选择 Generate ANTLR Recognizer 菜单去生成语法解析器。生成的文件如下:
    8f4b3d78eb234daba83f84a2b6090279

通过上述步骤我们就生成了一个语法解析器,接下来我们可以打开我们熟悉的fastjson API,开始做一些不可告人的事情。

模仿(抄袭)fastjson API

新建 JSONObject 类,我们知道 json 是由一系列 key-value 构成的结构,那么我们可以继承 LinkedHashMap ,你要觉得我说的没道理,你可以看看 fastjson 的实现,我就是借鉴(抄袭)它的。具体代码如下:

package com.geely.gson;

import com.geely.gson.support.JSONLexer;
import com.geely.gson.support.JSONParser;
import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.tree.TerminalNode;

import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.nio.channels.ReadableByteChannel;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;

/**
 * @author sunlong
 * @since 2023/9/12
 */
public class JSONObject extends LinkedHashMap {

    public JSONObject() {
    }

    public JSONObject(int initialCapacity) {
        super(initialCapacity);
    }

    public JSONObject(int initialCapacity, float loadFactor) {
        super(initialCapacity, loadFactor);
    }

    public JSONObject(int initialCapacity, float loadFactor, boolean accessOrder) {
        super(initialCapacity, loadFactor, accessOrder);
    }

    public JSONObject(Map map) {
        super(map);
    }

    protected JSONObject(JSONParser.ObjContext objContext) {
        for (JSONParser.PairContext pairContext : objContext.pair()) {
            this.put(stringWrapper(pairContext.STRING().getText()), pairContext.value());
        }
    }

    public static JSONObject parseObject(String text) {
        return parseObject(CharStreams.fromString(text));
    }

    public static JSONObject parseObject(ReadableByteChannel channel) throws IOException {
        return parseObject(CharStreams.fromChannel(channel));
    }

    public static JSONObject parseObject(Path path) throws IOException {
        return parseObject(CharStreams.fromPath(path));
    }

    public static JSONObject parseObject(InputStream inputStream) throws IOException {
        return parseObject(CharStreams.fromStream(inputStream));
    }

    public static JSONObject parseObject(Reader reader) throws IOException {
        return parseObject(CharStreams.fromReader(reader));
    }

    public static JSONObject parseObject(CharStream charStream) {
        JSONLexer lexer = new JSONLexer(charStream);
        CommonTokenStream tokens = new CommonTokenStream(lexer);
        JSONParser parser = new JSONParser(tokens);
        JSONParser.ObjContext objCtx = parser.obj();
        return new JSONObject(objCtx);
    }

    String stringWrapper(String origin) {
        return origin.substring(1, origin.length() - 1);
    }

    public JSONObject getJSONObject(String key) {
        return get(key, value -> {
            JSONParser.ObjContext objContext = value.obj();
            if (objContext == null) {
                return null;
            }
            return new JSONObject(objContext);
        });
    }

    public JSONArray getJSONArray(String key) {
        return get(key, value -> {
            JSONParser.ArrContext arrContext = value.arr();
            if (arrContext == null) {
                return null;
            }
            return new JSONArray(arrContext);
        });
    }

    public String getStr(String key) {
        return get(key, value -> {
            TerminalNode node = value.STRING();
            if (node == null) {
                return null;
            }
            return stringWrapper(node.getText());
        });
    }

    public Integer getInt(String key) {
        return get(key, value -> {
            TerminalNode node = value.NUMBER();
            if (node == null) {
                return null;
            }
            return Integer.valueOf(node.getText());
        });
    }

    public Long getLong(String key) {
        return get(key, value -> {
            TerminalNode node = value.NUMBER();
            if (node == null) {
                return null;
            }
            return Long.valueOf(node.getText());
        });
    }

    public Double getDouble(String key) {
        return get(key, value -> {
            TerminalNode node = value.NUMBER();
            if (node == null) {
                return null;
            }
            return Double.valueOf(node.getText());
        });
    }

    public Float getFloat(String key) {
        return get(key, value -> {
            TerminalNode node = value.NUMBER();
            if (node == null) {
                return null;
            }
            return Float.valueOf(node.getText());
        });
    }

    public Boolean getBoolean(String key) {
        return get(key, value -> {
            TerminalNode node = value.STRING();
            if (node == null) {
                return null;
            }
            return Boolean.valueOf(node.getText());
        });
    }

    @SuppressWarnings("unchecked")
    public  R get(String key, Function convert) {
        Object valueObj = this.get(key);
        if (valueObj == null) {
            return (R) null;
        }
        if (valueObj instanceof JSONParser.ValueContext) {
            JSONParser.ValueContext valueContext = (JSONParser.ValueContext) valueObj;
            if ("null".equalsIgnoreCase(valueContext.getText())) {
                return (R) null;
            }
            R result = convert.apply(valueContext);
            // 更新body树,下次获取不需要再次转换
            this.put(key, result);
        }
        return (R) this.get(key);
    }

    public String toJSONString() {
        StringBuilder sb = new StringBuilder();
        List list = new ArrayList(this.size());
        for (Map.Entry entry : this.entrySet()) {
            String key = entry.getKey();
            Object object = entry.getValue();
            String value;
            if (object == null) {
                value = null;
            } else if (object instanceof String) {
                value = """ + object + """;
            } else if (object instanceof JSONObject) {
                value = object.toString();
            } else if (object instanceof JSONArray) {
                value = object.toString();
            } else if (object instanceof Integer) {
                value = object.toString();
            } else if (object instanceof Double) {
                value = object.toString();
            } else if (object instanceof Float) {
                value = object.toString();
            } else if (object instanceof Boolean) {
                value = object.toString();
            } else if (object instanceof Long) {
                value = object.toString();
            } else {
                value = ((JSONParser.ValueContext) object).getText();
            }
            list.add(""" + key + "":" + value);
        }
        sb.append("{");
        sb.append(String.join(",", list));
        sb.append("}");
        return sb.toString();
    }

    @Override
    public String toString() {
        return toJSONString();
    }
}

我们知道 json 里的 value 类型支持数组,所以接下来我们需要实现一个 JSONArray 类,啊,你问我为什么这个名字这么熟悉?没错, fastjson 也是这个名字:

package com.geely.gson;

import com.geely.gson.support.JSONLexer;
import com.geely.gson.support.JSONParser;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author sunlong
 * @since 2023/9/12
 */
public class JSONArray extends ArrayList {

    public JSONArray() {

    }

    public JSONArray(int initialCapacity) {
        super(initialCapacity);
    }

    public JSONArray(Collection collection) {
        super(collection);
    }

    protected JSONArray(JSONParser.ArrContext arrayCtx) {
        addAll(arrayCtx.value()
                .stream()
                .map(valueContext -> new JSONObject(valueContext.obj()))
                .collect(Collectors.toList()));
    }

    public static JSONArray parseArray(String text) {
        JSONLexer lexer = new JSONLexer(CharStreams.fromString(text));
        CommonTokenStream tokens = new CommonTokenStream(lexer);
        JSONParser parser = new JSONParser(tokens);
        JSONParser.ArrContext arrContext = parser.arr();
        return new JSONArray(arrContext);
    }

    public String toJSONString() {
        StringBuilder sb = new StringBuilder();
        sb.append("[");
        List strings =
                this.stream()
                        .map(JSONObject::toJSONString)
                        .collect(Collectors.toList());
        sb.append(String.join(",", strings));
        sb.append("]");
        return sb.toString();
    }

    @Override
    public String toString() {
        return toJSONString();
    }
}

再包装一个更人性化的 Exception ,屏蔽掉 antlr4 内置的异常:

package com.geely.gson.exception;

/**
 * @author sunlong
 * @since 2023/9/12
 */
public class JsonException extends RuntimeException {
    public JsonException(String message) {
        super(message);
    }
}

好,如果你看到这里,那么咱们的fastjson就差不多完成了,接下来是骡子是马,让我们牵出来遛遛吧:

package com.geely.gson;

import java.io.IOException;
import java.io.InputStream;

/**
 * @author sunlong
 * @since 2023/9/12
 */
public class Main {
    public static void main(String[] args) throws IOException {
         String json ="{n" +
                "  "k1": "v1",n" +
                "  "k2": [n" +
                "    {n" +
                "      "k21": 3,n" +
                "      "k22": true,n" +
                "      "k23": null,n" +
                "      "k24": 1.5n" +
                "    }n" +
                "  ]n" +
                "}n";
        JSONObject root = JSONObject.parseObject(json);
        root.put("test", 1111);
        String k1 = root.getStr("k1");
        JSONArray k2 = root.getJSONArray("k2");
        for (JSONObject jsonObject : k2) {
            System.out.println(jsonObject.toJSONString());
        }
        System.out.println(root);
    }
}


输出结果如下:

c8ea55165e5f46b6b27ca4a24d84fcc3

perfect,洗洗睡觉。

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论