|
| 1 | +# 从零开始的 JSON 库教程 (Go语言版) |
| 2 | + |
| 3 | +* 基于 Milo Yip 的 C语言JSON教程改编 |
| 4 | +* 2025年 |
| 5 | + |
| 6 | +这是一个使用Go语言实现的JSON库教程,基于[《从零开始的 JSON 库教程》](https://github.com/miloyip/json-tutorial)改编。本教程保持原教程的章节结构和渐进式开发方法,但根据Go语言的特性进行了适当调整。 |
| 7 | + |
| 8 | +## 对象与目标 |
| 9 | + |
| 10 | +教程对象:学习过基本Go语言编程的同学。 |
| 11 | + |
| 12 | +通过这个教程,同学可以了解如何从零开始写一个Go语言版本的JSON库,其特性如下: |
| 13 | + |
| 14 | +* 符合标准的JSON解析器和生成器 |
| 15 | +* 使用Go语言的递归下降解析器(recursive descent parser) |
| 16 | +* 跨平台(如Windows/Linux/macOS) |
| 17 | +* 支持UTF-8 JSON文本 |
| 18 | +* 使用Go语言内置类型存储JSON数据 |
| 19 | +* 解析器和生成器的代码简洁高效 |
| 20 | +* 提供丰富的访问和修改API |
| 21 | + |
| 22 | +除了围绕JSON作为例子,希望能在教程中讲述一些课题: |
| 23 | + |
| 24 | +* 测试驱动开发(test driven development, TDD) |
| 25 | +* Go语言编程风格 |
| 26 | +* 数据结构 |
| 27 | +* API设计 |
| 28 | +* 错误处理 |
| 29 | +* Unicode |
| 30 | +* 浮点数 |
| 31 | +* Go模块、单元测试等工具和实践 |
| 32 | + |
| 33 | +## 项目结构 |
| 34 | + |
| 35 | +教程按照章节组织,每个章节对应一个子目录: |
| 36 | + |
| 37 | +1. tutorial01: 基础结构、解析null/true/false |
| 38 | +2. tutorial02: 解析数字 |
| 39 | +3. tutorial03: 解析字符串 |
| 40 | +4. tutorial04: Unicode支持 |
| 41 | +5. tutorial05: 解析数组 |
| 42 | +6. tutorial06: 解析对象 |
| 43 | +7. tutorial07: 生成器 |
| 44 | +8. tutorial08: 访问与其他功能 |
| 45 | +9. tutorial09: 增强的错误处理 |
| 46 | + |
| 47 | +## 当前进度 |
| 48 | + |
| 49 | +当前已经完成了所有教程的实现,包括: |
| 50 | + |
| 51 | +* null、true、false等字面值 |
| 52 | +* 数字(包括整数、浮点数、科学计数法) |
| 53 | +* 字符串(支持转义序列和Unicode) |
| 54 | +* 数组(支持嵌套) |
| 55 | +* 对象(支持嵌套) |
| 56 | +* JSON生成器 - 将JSON值转换为文本 |
| 57 | +* 高级功能: |
| 58 | + * 对象键值查询 |
| 59 | + * JSON值比较 |
| 60 | + * 深度复制、移动和交换 |
| 61 | + * 动态数组和对象操作 |
| 62 | + * 高效内存管理 |
| 63 | +* 增强错误处理: |
| 64 | + * 详细的错误信息与位置 |
| 65 | + * 错误恢复能力 |
| 66 | + * 自定义解析选项 |
| 67 | + * 支持JSON注释 |
| 68 | + |
| 69 | +## 安装与使用 |
| 70 | + |
| 71 | +要使用这个库,你需要Go语言环境(推荐Go 1.16或更高版本)。克隆该仓库后,可以直接导入使用: |
| 72 | + |
| 73 | +```go |
| 74 | +import "github.com/Cactusinhand/go-json-tutorial/tutorial08" |
| 75 | +// 或使用增强错误处理版本 |
| 76 | +import "github.com/Cactusinhand/go-json-tutorial/tutorial09" |
| 77 | + |
| 78 | +func main() { |
| 79 | + // 基本解析JSON |
| 80 | + v := leptjson.Value{} |
| 81 | + if err := leptjson.Parse(&v, `{"name": "John", "age": 30}`); err != leptjson.PARSE_OK { |
| 82 | + // 处理错误 |
| 83 | + } |
| 84 | + |
| 85 | + // 使用增强错误处理 |
| 86 | + options := leptjson.ParseOptions{ |
| 87 | + RecoverFromErrors: true, // 启用错误恢复 |
| 88 | + AllowComments: true, // 允许JSON注释 |
| 89 | + MaxDepth: 1000 // 设置最大嵌套深度 |
| 90 | + } |
| 91 | + if err := leptjson.ParseWithOptions(&v, `{"name": "John", /* 这是注释 */ "age": 30}`, options); err != leptjson.PARSE_OK { |
| 92 | + // 错误处理但仍能继续解析 |
| 93 | + fmt.Println("解析遇到错误但已恢复:", err) |
| 94 | + } |
| 95 | + |
| 96 | + // 访问解析后的数据 |
| 97 | + name := leptjson.GetString(leptjson.GetObjectValueByKey(&v, "name")) |
| 98 | + age := leptjson.GetNumber(leptjson.GetObjectValueByKey(&v, "age")) |
| 99 | + |
| 100 | + // 使用FindObjectKey快速访问 |
| 101 | + if value, found := leptjson.FindObjectKey(&v, "name"); found { |
| 102 | + name = leptjson.GetString(value) |
| 103 | + } |
| 104 | + |
| 105 | + // 修改JSON数据 |
| 106 | + newPerson := &leptjson.Value{} |
| 107 | + leptjson.SetObject(newPerson) |
| 108 | + nameValue := leptjson.SetObjectValue(newPerson, "name") |
| 109 | + leptjson.SetString(nameValue, "Alice") |
| 110 | + |
| 111 | + // 动态数组操作 |
| 112 | + arr := &leptjson.Value{} |
| 113 | + leptjson.SetArray(arr, 0) |
| 114 | + elem := leptjson.PushBackArrayElement(arr) |
| 115 | + leptjson.SetNumber(elem, 42) |
| 116 | + |
| 117 | + // 生成JSON字符串 |
| 118 | + jsonStr, _ := leptjson.Stringify(newPerson) |
| 119 | +} |
| 120 | +``` |
| 121 | + |
| 122 | +## 关键API说明 |
| 123 | + |
| 124 | +### 解析和生成 |
| 125 | +* `Parse(&v, json)`: 解析JSON文本到Value结构 |
| 126 | +* `ParseWithOptions(&v, json, options)`: 使用自定义选项解析JSON文本 |
| 127 | +* `Stringify(&v)`: 将Value结构转换为JSON文本 |
| 128 | + |
| 129 | +### 类型和值访问 |
| 130 | +* `GetType(&v)`: 获取JSON值的类型 |
| 131 | +* `GetBoolean(&v)`, `SetBoolean(&v, b)`: 获取/设置布尔值 |
| 132 | +* `GetNumber(&v)`, `SetNumber(&v, n)`: 获取/设置数字值 |
| 133 | +* `GetString(&v)`, `SetString(&v, s)`: 获取/设置字符串值 |
| 134 | + |
| 135 | +### 数组操作 |
| 136 | +* `GetArraySize(&v)`: 获取数组大小 |
| 137 | +* `GetArrayElement(&v, index)`: 获取数组元素 |
| 138 | +* `SetArray(&v, capacity)`: 设置为数组类型 |
| 139 | +* `PushBackArrayElement(&v)`: 添加数组元素 |
| 140 | +* `PopBackArrayElement(&v)`: 移除最后一个元素 |
| 141 | +* `InsertArrayElement(&v, index)`: 插入元素 |
| 142 | +* `EraseArrayElement(&v, index, count)`: 删除元素 |
| 143 | +* `ClearArray(&v)`: 清空数组 |
| 144 | + |
| 145 | +### 对象操作 |
| 146 | +* `GetObjectSize(&v)`: 获取对象大小 |
| 147 | +* `GetObjectKey(&v, index)`: 获取对象键 |
| 148 | +* `GetObjectValue(&v, index)`: 获取对象值 |
| 149 | +* `GetObjectValueByKey(&v, key)`: 根据键获取对象值 |
| 150 | +* `FindObjectKey(&v, key)`: 查找键并返回值 |
| 151 | +* `SetObject(&v)`: 设置为对象类型 |
| 152 | +* `SetObjectValue(&v, key)`: 设置对象键值 |
| 153 | +* `RemoveObjectValue(&v, index)`: 移除成员 |
| 154 | +* `ClearObject(&v)`: 清空对象 |
| 155 | + |
| 156 | +### 内存和资源管理 |
| 157 | +* `Copy(dst, src)`: 深度复制JSON值 |
| 158 | +* `Move(dst, src)`: 移动JSON值 |
| 159 | +* `Swap(lhs, rhs)`: 交换两个JSON值 |
| 160 | +* `Free(&v)`: 释放资源 |
| 161 | + |
| 162 | +### 比较和其他 |
| 163 | +* `Equal(lhs, rhs)`: 比较两个JSON值是否相等 |
| 164 | + |
| 165 | +### 增强错误处理 |
| 166 | +* `ParseOptions`: 定义解析选项的结构体,包含错误恢复、注释支持和最大深度等选项 |
| 167 | +* `EnhancedError`: 提供详细的错误信息,包括行号、列号、上下文和错误位置指示 |
| 168 | +* `GetErrorMessage(code)`: 获取特定错误码的错误描述信息 |
| 169 | + |
| 170 | +## 测试 |
| 171 | + |
| 172 | +每个章节都包含完整的测试用例,测试覆盖了各种正常和边缘情况。运行测试: |
| 173 | + |
| 174 | +```bash |
| 175 | +go test ./tutorial08 |
| 176 | +``` |
| 177 | + |
| 178 | +## 注意事项 |
| 179 | + |
| 180 | +* 处理Unicode时,需要特别注意UTF-16代理对的处理 |
| 181 | +* 数字解析需要考虑各种边缘情况,如前导零、溢出等 |
| 182 | +* 字符串解析需要正确处理所有转义序列 |
| 183 | +* 数组和对象解析需要处理嵌套和边界情况 |
| 184 | +* 在使用动态数组和对象函数时,注意内存管理和资源释放 |
| 185 | + |
| 186 | +## 参考资料 |
| 187 | + |
| 188 | +* [JSON官方规范](https://www.json.org/) |
| 189 | +* [原始C语言教程](https://github.com/miloyip/json-tutorial) |
| 190 | +* [RFC 7159: The JavaScript Object Notation (JSON) Data Interchange Format](https://tools.ietf.org/html/rfc7159) |
| 191 | +* [RFC 3629: UTF-8, a transformation format of ISO 10646](https://tools.ietf.org/html/rfc3629) |
0 commit comments