likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 19

背景

最近在学习编译原理的过程中了解到有些语言的编译器前端是利用 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
   : [ \t\n\r] + -> skip
   ;
// + 表示 匹配前一个匹配项至少一次,skip表示跳过,本句的意思是匹配到空格、制表符、回车符或换行符则跳过

测试语法文件

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

生成语法解析器

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

  • 配置插件 在g4文件中右键选择 Configure ANTLR... 按钮,进行一些简单的配置即可: 一起来实现一个 "fast"json 序列化工具吧

  • 生成目标文件 我们可以在 g4 文件中右键选择 Generate ANTLR Recognizer 菜单去生成语法解析器。生成的文件如下: 一起来实现一个 "fast"json 序列化工具吧

通过上述步骤我们就生成了一个语法解析器,接下来我们可以打开我们熟悉的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<String, Object> {

    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<String, Object> 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> R get(String key, Function<JSONParser.ValueContext, R> 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<String> list = new ArrayList<>(this.size());
        for (Map.Entry<String, Object> 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<JSONObject> {

    public JSONArray() {

    }

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

    public JSONArray(Collection<JSONObject> 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<String> 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.5\n" +
                "    }\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);
    }
}


输出结果如下:

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

perfect,洗洗睡觉。