JSON 由对象和数组组成,对象是一个 Key 和 Value 的关系映射,数组则是由多个 value 组成。对象使用大括号 {} 表示,数组则使用中括号 [] 表示。因此我们可以得到对象和数组的表达式
object: '{' '}' | '{' kv (',' kv)* '}';
array: '[' ']' | '[' v (',' v)* ']';
其中 kv 表示一个 KeyValue 的关系映射,多个 KeyValue 中间使用逗号 , 分割,一个对象可以不包含元素,或者包含一个或多个元素。v 表示一个元素,数组的多个元素中间同样使用 , 分割,数组可以没有元素,也可以包含一个或多个元素。
kv 由 key 和 value 组成,key 只需要是一个字符串就可以,而对象的 value 和数组的 value 一样,支持以下多种元素
由此可以得到 kv 和 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]+)?;
字符串需要由双引号包住,并且内部不能有双引号;数字则可以是带有负号的小数。
完整的 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,继承这个类,在继承的子类中解析语法树
flat1 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 格式化
flat1 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.removeErrorListeners(); lexer.addErrorListener(new JsonErrorHandler());
CommonTokenStream tokens = new CommonTokenStream(lexer); JsonParser parser = new JsonParser(tokens); parser.removeErrorListeners(); 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 代码
本文链接: https://www.nosuchfield.com/2023/09/02/Formatting-JSON-with-ANTLR4/