JSON解析

Soul Lv1

既然要手写一个可用的spring框架,那么网络通信部分是必不可少的,既然涉及到了网络通信,那么显然json解析能力是必须具备的,所以我们今天来试着手写一个json解析器吧

JSON的规则

首先我们研究以下JSON解析的问题,开始之前我们可以先看看JSON的标准

JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,易于阅读和编写,也易于机器解析和生成。以下是 JSON 的主要规则和特点:

1. 基本结构

  • JSON 数据由两种结构组成:
    • 对象(Object):无序的键值对集合,用花括号 {} 表示。
    • 数组(Array):有序的值的集合,用方括号 [] 表示。

2. 数据类型

JSON 支持以下数据类型:

  • 字符串:用双引号 " 括起来的文本,例如 "Hello, World!"
  • 数字:整数或浮点数,例如 423.14
  • 布尔值truefalse
  • 空值null
  • 对象:键值对的集合,例如 { "name": "Alice", "age": 30 }
  • 数组:值的有序列表,例如 [1, 2, 3, "apple"]

3. 键值对规则

  • 键(Key)必须是字符串,并且用双引号 " 括起来。
  • 键和值之间用冒号 : 分隔。
  • 键值对之间用逗号 , 分隔。
  • 示例:
    1
    2
    3
    4
    5
    {
    "name": "Alice",
    "age": 30,
    "isStudent": false
    }

4. 数组规则

  • 数组中的值可以是任何 JSON 支持的数据类型。
  • 数组中的值用逗号 , 分隔。
  • 示例:
    1
    2
    3
    4
    5
    6
    7
    [
    "apple",
    "banana",
    42,
    true,
    { "name": "Alice" }
    ]

5. 嵌套结构

  • JSON 支持嵌套的对象和数组。
  • 示例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    {
    "name": "Alice",
    "age": 30,
    "hobbies": ["reading", "traveling"],
    "address": {
    "street": "123 Main St",
    "city": "New York"
    }
    }

6. 格式规范

  • JSON 数据必须是有效的对象或数组。
  • 键必须用双引号括起来(单引号无效)。
  • 逗号不能出现在最后一个键值对或数组值之后(即不能有尾随逗号)。
  • 示例(错误):
    1
    { "name": "Alice", }  // 错误:尾随逗号

7. JSON 的用途

  • 用于 Web 应用程序中的数据交换。
  • 用于配置文件(如 .json 文件)。
  • 用于 API 数据传输。

8. JSON 与 JavaScript 的关系

  • JSON 是 JavaScript 的一个子集,但独立于语言,可以被多种编程语言解析和生成。

总结来说,JSON 是一种简单、灵活且广泛使用的数据格式,遵循上述规则可以确保数据的正确性和可读性。

上面这部分是直接从网上找到的相关规范。仔细考虑以下,该怎们做?

尝试解析

我随便贴一段json,让我们来分析以下我们可能要处理的情况

1
2
3
4
5
6
7
8
9
{
"name": "Alice",
"age": 30,
"hobbies": ["reading", "traveling"],
"address": {
"street": "123 Main St",
"city": "New York"
}
}

我们可能遇到的情况包括:

  • 遇到{,表示一层解析的开始,如果在本层解析过程中遇到了新的{,则进入新的解析层,换句话说,我们需要一个递归结构来处理问题
  • 遇到},表示当前层的解析结束
  • 遇到,表示某个key或者value,可以考虑通过识别来辨别这是一个key还是value,
  • 遇到[,表示数组的开始
  • 遇到,,表示键值对之间的分割
  • 遇到/,转义字符,后面的一个字符需要转义,

仔细思考上面的信息,我们可以大概的写出一个可用的demo(建议先自己尝试一下,还是有一点难度的)

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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
package Winter.Parser.JsonPaser;

import java.util.*;

public class JsonParser {
String json;
int index;

private JsonParser(String json) {
this.json = json;
this.index = 0;
}

public void skipWhiteSpace() {
while (Character.isWhitespace(json.charAt(index))) {
index++;
}
}//跳过空格的方法

public void expectString(String target) {
int start = index;
for (int i = 0; i < target.length(); i++) {if (target.charAt(i) != json.charAt(start)) {
throw new RuntimeException("Expecting string at index " + index + " but found " + target.charAt(i));
}
start++;
}//用于检测程序是否是按照我们期望的方式进行
}

public Object parseValue() {
skipWhiteSpace();
char ch = json.charAt(index);
return switch (ch) {
case '{' -> parseObject();//通过首个字符类型判断解析方法
case '[' -> parseArray();
case '"' -> parseString();
case 't' -> parseTrue();
case 'f' -> parseFalse();
case 'n' -> parseNull();
default -> {
if (ch == '-' || Character.isDigit(ch)) {
yield parseNumber();
}//处理判断数字的情况
throw new RuntimeException("Unexpected character at position " + index + ": " + ch);
}
};
}

private Object parseObject() {
Map<String, Object> jsonNodeMap = new LinkedHashMap<>();
expectString("{");
index++;
skipWhiteSpace();
if(json.charAt(index)=='}'){
index++;
return jsonNodeMap;
}
while (true){
skipWhiteSpace();
String key = parseString();//由于key一定是字符串,直接用字符串的方式解析
skipWhiteSpace();
expectString(":");
index++;//跳过冒号
Object value = parseValue();//获取value
jsonNodeMap.put(key,value);
if(json.charAt(index)=='}'){break;}
if(json.charAt(index) !=','){
throw new RuntimeException("Unexpected character '" + json.charAt(index) + "' at index " + index);
}
index++;//用于跳过键值对之间的逗号
}
return jsonNodeMap;
}

private String parseString() {
StringBuilder stringBuilder = new StringBuilder();
expectString("\"");
index++;//跳过引号
skipWhiteSpace();
while (json.charAt(index) != '"') {//处理可能的转义字符
if (json.charAt(index) == '\\') {
index++;//跳过用于转义的\
char c = json.charAt(index);
switch (c) {
case 't' -> stringBuilder.append("\t");
case 'f' -> stringBuilder.append("\f");
case 'n' -> stringBuilder.append("\n");
case 'b' -> stringBuilder.append("\b");
case 'r' -> stringBuilder.append("\r");
case '"' -> stringBuilder.append("\"");
case '/' -> stringBuilder.append("/");
case '\\' -> stringBuilder.append("\\");
case 'u' -> {//处理特殊的unicode转义
if (index + 4 >= json.length()) {
throw new RuntimeException("Unexpected unicode sequence at index " + index);
}
stringBuilder.append((char) Integer.parseInt(json.substring(index+1, index + 4), 16));
index += 4;
}
default -> throw new RuntimeException("Unexpected character '" + c + "' at index " + index);
}
index++;//跳过被转义的字符

} else {
stringBuilder.append(json.charAt(index));
index++;
}

}
index++;//跳过字符串末尾的引号
return stringBuilder.toString();
}

private Number parseNumber() {
skipWhiteSpace();
int start = index;
if(json.charAt(index)=='-'){
index++;
}
while (index < json.length() && Character.isDigit(json.charAt(index))) {index++;}
if(json.charAt(index)=='.'){//处理浮点数
do {
index++;
} while (index < json.length() && Character.isDigit(json.charAt(index)));
}
if(json.charAt(index)=='e' || json.charAt(index)=='E'){//处理科学计数法
do {
index++;
} while (index < json.length() && Character.isDigit(json.charAt(index)));
}
String number = json.substring(start,index);//返回解析得到的数字
if(number.contains("e") || number.contains("E") || number.contains(".")){
return Double.parseDouble(number);
}else {
try {
return Integer.parseInt(number);
}catch (NumberFormatException e){
return Long.parseLong(number);
}
}
}
public List<Object> parseArray() {
skipWhiteSpace();
List<Object> list = new ArrayList<>();
expectString("[");
index++;
skipWhiteSpace();
while (json.charAt(index) != ']') {
skipWhiteSpace();
list.add(parseValue());
if(json.charAt(index)!=']'){index++;}//用于跳过数组元素之间的逗号
}
index++;//跳过]
return list;
}

private boolean parseFalse() {
expectString("false");
index+=5;
return false;
}

private boolean parseTrue(){
expectString("true");
index+=4;
return true;
}
private Object parseNull() {
expectString("null");
index+=4;
return null;
}

public static void main(String[] args) {
String json = "{\"greeting\":\"你好,世界!\",\"farewell\":\"再见,朋友!\"}";
JsonParser parser = new JsonParser(json);
Object value = parser.parseValue();
System.out.println(value);

}

}

感觉如何?如果能够理清所有的逻辑,自己独立写一个还是比较简单的,毕竟也就不到200行的代码量。当然,这个解析器从效率上来将肯定没有办法和一些主流的库比,但不管怎么说,这玩意能用了。

建议去找ai写几个测试用例试一试(要求不要进行格式化),这个解析器其实只考虑了一些基本情况,你可以试着补足

查询支持

在完成解析后,我们肯定要做一下查询的支持,这样才能方便我们下一步与框架的继承,其实有上面的基础,这一步非常简单,基本的思路如下

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
public class JSON {
private final Map<String,Object> jsonMap;


public JSON(String json) {
JsonParser jp = new JsonParser(json);
Object ret = jp.parseValue();
if(!(ret instanceof Map)){
throw new RuntimeException("JSON parsing failed");
}
this.jsonMap = (Map<String, Object>) ret;
}
// 新增查询方法
public List<Object> query(String path) {
List<Object> result = new ArrayList<>();
String[] keys = path.split("\\.");
queryRecursive(jsonMap, keys, 0, result);
return result;
}//查询方法
//用于递归查询
private void queryRecursive(Object value, String[] keys, int depth, List<Object> result) {
if (depth >= keys.length) {//如果递归深度达到了数组长度,停止递归,将结果保存到list中
result.add(value);
return;
}

String key = keys[depth];
if (value instanceof Map) {//如果是一堆嵌套的map,反复的递归直到指定层数
Map<String, Object> map = (Map<String, Object>) value;
if (map.containsKey(key)) {
queryRecursive(map.get(key), keys, depth + 1, result);
}
} else if (value instanceof List) {
List<Object> list = (List<Object>) value;//如果发现数组类型,遍历数组类型的所有元素
for (Object item : list) {//用于处理数组类型
queryRecursive(item, keys, depth, result);
}
}
}
}

怎么样,写起来也不是非常困难,反正就是不断递归查找。

当然,这个方法也存在问题,首先,返回的是一个list,使用的时候还要做各种类型转换与遍历。但不管怎么说,这玩意能用了,这就是好事。

结语

今天就到这里了,嘴上说简单,但还是写的头疼,是在写不动了,先就此打住吧,下一期实现一个线程池,再下一期写一个请求转发器,最后再补充一些事务方面的东西,支持一下数据库交互,这套框架就差不多将就这能用了

  • 标题: JSON解析
  • 作者: Soul
  • 创建于 : 2025-04-07 17:46:03
  • 更新于 : 2025-04-13 15:56:51
  • 链接: https://soulmate.org.cn/2025/04/07/JSON解析/
  • 版权声明: 本文章采用 CC BY-NC 4.0 进行许可。