头图

介绍

前面两节我们介绍了分词和分词清洗,经过清洗后的token列表已经可以用来生产语法树,本节我们将实现语法树的生成,语法树的目的就是将数据结构化,什么叫结构化,比如字符串表达式(1+(2*3))要写个程序计算这个结果还是有点难的,因为这个字符串只是一个字符串,我们需要去解析,那么这个字符串就不是一个结构化的数据,如果给你的是解析后的数据,那么就很好处理,解析后的数据就是结构化的数据,你可能会说那不就是分词吗,其实不是的,也是以上面那个表达式为例,分词都是平铺结构,没有层级关系,但表达式有优先级,嵌套等层级关系,这个就是语法树要做的事。

实现

难点

生成语法树的难点在于,token数组是平铺结构,但语法树是树形结构,有层级关系,我们需要将将平铺结构变成树形结构,当token数组中包含object和array时语法树就会有新的分支,因此重点就是要处理好object和array,这两个处理方式本质上是一致的,可以简化为找括号问题,什么意思呢,比如一个表达式(1+(2+(3+4)+5)+6)如何找到成对的括号,一种简单的方法是如果碰到(就进入递归,直到碰到)退出,这样最后一次递归会先匹配第一个)和我们想要的结果相符,有点类似栈,后进先出,就以(1+(2+(3+4)+5)+6)为例,总共17个字符,括号的匹配情况应该是

0匹配16
3匹配13
6匹配10

代码实现

public static void test(String text) {
        int begin = 0;
        int end = 0;
        while (index < text.length()) {
            int c = text.charAt(index);
            if (c == '(') {
                begin = index;
                char cc = text.charAt(++index);
                while (cc != ')') {
                    test(text);
                    cc = text.charAt(index);
                }
                end = index;
                System.out.println(String.format("%d匹配%d", begin, end));
            } else if (c == ')') {
                break;
            }
            ++index;
        }
    }

输出

6匹配10
3匹配13
0匹配16

因为是最里面括号开始匹配所以先输出6匹配10,结果是符合预期的,理解了这个就很容易理解语法树的生成过程

实现

首先定义抽象语法树数据结构,如下

public class Ast {
    //节点类型
    private String type;

    //如果是object则是字段列表
    //如果是array则是值列表
    private List<Ast> items;

    //字段名(可为空)
    private String name;

    //字段值
    private Object value;
  ....
}

语法树构建步骤如下

  • 如果是object和array,那么就按照递归的方式进行遍历
  • 如果是key,那么根据value的类型进行处理,如果是object和array,那么按照递归的方式进行遍历,如果是基本类型,直接处理
  • 如果是value,直接处理

代码如下:

/**
 * 生成抽象语法树
 *
 * @return
 */
public List<Ast> generateAST() {
    List<Ast> items = new ArrayList<>();
    //currentIndex作为当前读取token的位置
    //取出对象则currentIndex需要往前移动
    Token token = jsonTokens.get(currentIndex);
    if ("object".equals(token.getType()) && "{".equals(token.getValue())) {
        Ast item = new Ast();
        item.setType(token.getType());
        //如果json语法错误currentIndex有可能超过范围,这里先不考虑语法错误问题
        Token tt = jsonTokens.get(++currentIndex);
        while (!"object".equals(tt.getType()) || ("object".equals(tt.getType()) && !"}".equals(tt.getValue()))) {
            item.getItems().addAll(generateAST());
            tt = jsonTokens.get(currentIndex);
        }
        items.add(item);
        ++currentIndex;
    } else if ("array".equals(token.getType()) && "[".equals(token.getValue())) {
        Ast item = new Ast();
        item.setType(token.getType());
        Token tt = jsonTokens.get(++currentIndex);
        while (!"array".equals(tt.getType()) || ("array".equals(tt.getType()) && !"]".equals(tt.getValue()))) {
            item.getItems().addAll(generateAST());
            tt = jsonTokens.get(currentIndex);
        }
        items.add(item);
        ++currentIndex;
    } else if ("key".equals(token.getType())) {
        // key和value是成对出现,如果只有key没有value说明json语法错误,这里暂时不考虑语法错误
        Token value = jsonTokens.get(++currentIndex);
        if ("object".equals(value.getType()) || "array".equals(value.getType())) {
            //对象和数组需要递归获取语法树
            List<Ast> tts = generateAST();
            //如果是key-value结构必须设置key的名称
            tts.get(0).setName((String) token.getValue());
            return tts;
        } else if ("value".equals(value.getType())) {
            Ast item = new Ast();
            item.setValue(value.getValue());
            item.setType(value.getType());
            item.setName((String) token.getValue());
            items.add(item);
            ++currentIndex;
        }
    } else if ("value".equals(token.getType())) {
        //只有value没有key的情况,比如["1",2,true,null]
        Ast item = new Ast();
        item.setValue(token.getValue());
        item.setType(token.getType());
        items.add(item);
        ++currentIndex;
    }
    return items;
}

代码看着挺长,但就是做上面说的三件事,里面的逻辑并不复杂

测试

语法树结构不容易打印出来,我们用fastjson转化为json进行打印

public class Main {
    public static void main(String[] args) throws Exception {
        InputStream fin = Main.class.getResourceAsStream("/example.json");
        byte[] buf = new byte[fin.available()];
        fin.read(buf);
        fin.close();
        String json = new String(buf, "utf-8");
        JSONParser parser = new JSONParser();
        List<Token> tokens = parser.tokenizer(json);
        tokens = parser.tokenClean(tokens);
        List<Ast> astItems = parser.generateAST();
        Ast ast = astItems.get(0);
        System.out.println(String.format("|%-12s|%-12s|%-15s|", "type", "valueType", "value"));
        System.out.println("-------------------------------------------");
        for (Token t : tokens) {
            System.out.println(String.format("|%-12s|%-12s|%-15s|",
                    t.getType(),
                    t.getValueType(),
                    t.getValue()));
        }
        System.out.println("-------------------------------------------");
        System.out.println(JSON.toJSONString(ast, true));
    }
}

结果:

{
    "items":[
        {
            "name":"name",
            "type":"value",
            "value":"asan"
        },
        {
            "name":"age",
            "type":"value",
            "value":32
        },
        {
            "name":"married",
            "type":"value",
            "value":true
        },
        {
            "name":"birthday",
            "type":"value",
            "value":"1992-02-08"
        },
        {
            "name":"salary",
            "type":"value",
            "value":1234.56
        },
        {
            "name":"description",
            "type":"value",
            "value":"a \"hudsom\" man"
        },
        {
            "items":[
                {
                    "type":"value",
                    "value":"coder"
                },
                {
                    "type":"value",
                    "value":"mid-age"
                }
            ],
            "name":"tags",
            "type":"array"
        },
        {
            "items":[
                {
                    "name":"province",
                    "type":"value",
                    "value":"福建省"
                },
                {
                    "name":"city",
                    "type":"value",
                    "value":"泉州市"
                },
                {
                    "name":"area",
                    "type":"value",
                    "value":"晋江市"
                }
            ],
            "name":"location",
            "type":"object"
        },
        {
            "items":[
                {
                    "items":[
                        {
                            "name":"relation",
                            "type":"value",
                            "value":"couple"
                        },
                        {
                            "name":"name",
                            "type":"value",
                            "value":"Helen"
                        }
                    ],
                    "type":"object"
                },
                {
                    "items":[
                        {
                            "name":"relation",
                            "type":"value",
                            "value":"daughter"
                        },
                        {
                            "name":"name",
                            "type":"value",
                            "value":"XiaoMan"
                        }
                    ],
                    "type":"object"
                }
            ],
            "name":"family",
            "type":"array"
        }
    ],
    "type":"object"
}

这样我们成功构建了一棵语法树

代码

完整代码请参考项目https://github.com/wls1036/tiny-json-parser的0x03分支


DQuery
300 声望93 粉丝

幸福是奋斗出来的