JSON 由对象和数组组成,对象是一个 Key 和 Value 的关系映射,数组则是由多个 value 组成。对象使用大括号 {} 表示,数组则使用中括号 [] 表示。因此我们可以得到对象和数组的表达式

object: '{' '}' | '{' kv (',' kv)* '}';
array: '[' ']' | '[' v (',' v)* ']';

其中 kv 表示一个 KeyValue 的关系映射,多个 KeyValue 中间使用逗号 , 分割,一个对象可以不包含元素,或者包含一个或多个元素。v 表示一个元素,数组的多个元素中间同样使用 , 分割,数组可以没有元素,也可以包含一个或多个元素。

kv 由 key 和 value 组成,key 只需要是一个字符串就可以,而对象的 value 和数组的 value 一样,支持以下多种元素

  • 对象
  • 数组
  • 文本
  • 数字
  • true
  • false
  • null

由此可以得到 kvv 的表达式如下

kv: STRING ':' v;
v:
    STRING      # value
    | NUMBER    # value
    | object    # objectValue
    | array     # arrayValue
    | 'true'    # value
    | 'false'   # value
    | 'null'    # value;

其中字符串和数字的规则如下

STRING: '"' ~'"'+ '"';
NUMBER: '-'? [0-9]+ ('.' [0-9]+)?;

字符串需要由双引号包住,并且内部不能有双引号;数字则可以是带有负号的小数。

完整的 Json.g4 如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
grammar Json;

@header {
package com.nosuchfield.json.code;
}

json: (object | array) EOF;
object: '{' '}' | '{' kv (',' kv)* '}';
array: '[' ']' | '[' v (',' v)* ']';
kv: STRING ':' v;
v:
STRING # value
| NUMBER # value
| object # objectValue
| array # arrayValue
| 'true' # value
| 'false' # value
| 'null' # value;

STRING: '"' ~'"'+ '"';
NUMBER: '-'? [0-9]+ ('.' [0-9]+)?;

WS: [ \t\r\n]+ -> skip;

通过以上规则生成 JsonBaseListener.java,继承这个类,在继承的子类中解析语法树

flat
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
public class JsonListener extends JsonBaseListener {

/**
* 逗号加换行
*/
private final String COMMA_NEWLINE = ",\n";

/**
* 缩进
*/
private String indent = "";

/**
* 添加缩进
*/
private void addTab() {
indent += " ";
}

/**
* 移除缩进
*/
private void removeTab() {
indent = indent.replaceFirst(" {2}", "");
}

/**
* 结果
*/
private StringBuilder result = new StringBuilder();

@Override
public void enterJson(JsonParser.JsonContext ctx) {
if (ctx.getChild(0) instanceof JsonParser.ArrayContext) {
result.append("[\n");
addTab();
}
if (ctx.getChild(0) instanceof JsonParser.ObjectContext) {
result.append("{\n");
addTab();
}
}

@Override
public void exitJson(JsonParser.JsonContext ctx) {
if (ctx.getChild(0) instanceof JsonParser.ArrayContext) {
result = removeLastComma(result);
result.append("]");
removeTab();
}
if (ctx.getChild(0) instanceof JsonParser.ObjectContext) {
result = removeLastComma(result);
result.append("}");
removeTab();
}
}

@Override
public void enterObjectValue(JsonParser.ObjectValueContext ctx) {
if (ctx.getParent() instanceof JsonParser.ArrayContext) {
result.append(indent);
}
result.append("{\n");
addTab();
}

@Override
public void exitObjectValue(JsonParser.ObjectValueContext ctx) {
result = removeLastComma(result);
removeTab();
result.append(indent).append("}" + COMMA_NEWLINE);
}

@Override
public void enterArrayValue(JsonParser.ArrayValueContext ctx) {
if (ctx.getParent() instanceof JsonParser.ArrayContext) {
result.append(indent);
}
result.append("[\n");
addTab();
}

@Override
public void exitArrayValue(JsonParser.ArrayValueContext ctx) {
result = removeLastComma(result);
removeTab();
result.append(indent).append("]" + COMMA_NEWLINE);
}

@Override
public void enterKv(JsonParser.KvContext ctx) {
result.append(indent).append(ctx.STRING().getText()).append(": ");
}

@Override
public void exitValue(JsonParser.ValueContext ctx) {
if (ctx.getParent() instanceof JsonParser.ArrayContext) {
result.append(indent);
}
result.append(ctx.getText()).append(COMMA_NEWLINE);
}

public String getResult() {
return result.toString();
}

/**
* 将最后的逗号加换行替换为换行
*/
private StringBuilder removeLastComma(StringBuilder data) {
if (!data.toString().endsWith(COMMA_NEWLINE)) {
return data;
}
data = new StringBuilder(data.substring(0, data.length() - 2));
data.append("\n");
return data;
}

}

随后我们对上面的语法树解析进行测试,我们这里先新增一个错误处理器,它可以在解析出错的时候打印我们所需要的错误信息,并且退出程序

1
2
3
4
5
6
7
8
public class JsonErrorHandler extends BaseErrorListener {
@Override
public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, int line, int charPositionInLine, String msg, RecognitionException e) {
System.err.printf("failed: %s\n", msg);
System.err.printf(" at %s:%s:%d\n", recognizer.getInputStream().getSourceName(), line, charPositionInLine);
System.exit(1);
}
}

上面的程序会在解析出错的时候打印错误消息、源文件名称、出错行号、出错位置等信息。最后我们调用语法解析和错误处理实现 JSON 格式化

flat
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class TestJson {

@Test
public void testJson() throws IOException {
JsonLexer lexer = new JsonLexer(CharStreams.fromFileName("src/main/resources/json/data.json"));
// 移除Lexer默认的错误处理器
lexer.removeErrorListeners();
// 给Lexer新增错误处理器
lexer.addErrorListener(new JsonErrorHandler());

CommonTokenStream tokens = new CommonTokenStream(lexer);
JsonParser parser = new JsonParser(tokens);
// 移除Parser默认的错误处理器
parser.removeErrorListeners();
// 新增Parser自定义的错误处理器
parser.addErrorListener(new JsonErrorHandler());

ParseTree tree = parser.json();
System.out.println(tree.toStringTree(parser));

ParseTreeWalker parseTreeWalker = new ParseTreeWalker();
JsonListener listener = new JsonListener();
parseTreeWalker.walk(listener, tree);
System.out.println(listener.getResult());
}

}

给以上代码提供输入如下(测试数据)

1
{"jiblisca":true,"untqhvhmvm":"xgQwUW39uJ5ypfQpOKRY","jnjibur":268757502,"lqpfrifpio":["NQRib805YL0XJn",[[false,[-1580033815.1137862,["yHv",false,"udDTLXohYfPu-PyG",1332240391],518407756.13681334,{"ctuysyrz":[true,"0NFL","jec6pJw",{"cwqfrhum":-783147957.874186,"xtubtlfckuao":-368517848.71591294,"ywrahr":"MIR","jfaehyg":true,"fbzoxj":"2ntMW"},172065695,[false,-1637596578,{"iwxowaesjgo":["k-zAI",2057666098,"zWQEyHPRNR8vuy-zN",false,-1106132231.291643,true,true],"zcohkx":-185864 …(测试数据全文 151639 字符,此处省略)

执行完毕可以得到输出如下

(json (object { (kv "jiblisca" : (v true)) , (kv "untqhvhmvm" : (v "xgQwUW39uJ5ypfQpOKRY")) , (kv "jnjibur" : (v 268757502)) , (kv "lqpfrifpio" : (v (array [ (v "NQRib805YL0XJn") , (v (array [ (v (array [ (v false) , (v (array [ (v -1580033815.1137862) , (v (array [ (v "yHv") , (v false) , (v "udDTLXohYfPu-PyG") , (v 1332240391) ])) , (v 518407756.13681334) , (v (object { (kv "ctuysyrz" : (v (array [ (v true) , (v "0NFL") , (v "jec6pJw") , (v (object { (kv "cwqfrhum" : (v …(解析树全文 261142 字符,此处省略)
{
  "jiblisca": true,
  "untqhvhmvm": "xgQwUW39uJ5ypfQpOKRY",
  "jnjibur": 268757502,
  "lqpfrifpio": [
    "NQRib805YL0XJn",
    [
      [
        false,
        [
          -1580033815.1137862,
          [
            "yHv",
            false,
            "udDTLXohYfPu-PyG",
            1332240391
          ],
          518407756.13681334,
          {
            "ctuysyrz": [
              true,
              "0NFL",
              "jec6pJw",
              {

…(格式化输出共 11752 行,此处省略)
}

可以看到 JSON 已经被格式化了。

我们还可以修改输入为一个不合法的 JSON,例如

1
2
3
4
{
"name": "Ray",
"age": data
}

解析上面的数据可以得到报错信息如下,即 data 字符串是不合法的,错误位于文件的第 3 行第 9 列

failed: token recognition error at: 'd'
    at src\main\resources\json\data.json:3:9

参考

ANTLR4 规则
Java 代码