目录
一、项目目标
- 基础功能:
- 实现 JSON 数据的解析(Parsing)和序列化(Serialization)。
- 支持 JSON 的基本数据类型:字符串、数字、布尔值、
null
、数组、对象(键值对集合)。 - 支持嵌套结构(如数组中嵌套对象,对象中嵌套数组)。
- 扩展性:
- 提供清晰的接口,便于后续扩展(如支持注释、二进制格式转换等)。
- 支持自定义错误处理机制(如语法错误提示、非法输入检测)。
- 性能与健壮性:
- 优化内存管理,避免内存泄漏。
- 处理非法输入(如格式错误、类型不匹配)时提供友好的错误信息。
二、功能需求
JSON 解析(Parsing)
- 输入:符合 JSON 格式的字符串(如
"{\"name\":\"Alice\",\"age\":25}"
)。 - 输出:内存中的 JSON 数据结构(如树形结构或嵌套映射)。
- 支持语法:
- 基本类型:字符串(带双引号)、数字(整数/浮点数)、布尔值(
true
/false
)、null
。 - 复合类型:数组(
[...]
)、对象({...}
)。 - 转义字符:
\n
、\t
、\"
、\\
等。
- 基本类型:字符串(带双引号)、数字(整数/浮点数)、布尔值(
- 错误处理:
- 检测并报告语法错误(如缺少引号、括号不匹配)。
- 忽略合法的空白字符(空格、换行符、制表符)。
- 输入:符合 JSON 格式的字符串(如
JSON 序列化(Serialization)
- 输入:内存中的 JSON 数据结构。
- 输出:符合 JSON 格式的字符串。
- 格式要求:
- 字符串需用双引号包裹。
- 数字无需引号,布尔值和
null
使用关键字。 - 对象的键必须为字符串(带引号)。
- 支持缩进格式(可选,用于美观化输出)。
扩展功能(后续阶段)
- 支持注释(如
//
或/* */
,需标注为非标准 JSON)。 - 支持从文件读取/写入 JSON 数据。
- 支持与其他数据格式转换(如 XML、YAML)。
- 支持注释(如
三、实现思路
数据结构设计
- 核心类:
JsonNode
:表示 JSON 的任意节点,支持多态(继承自基类)。- 子类:
JsonString
,JsonNumber
,JsonBoolean
,JsonNull
,JsonArray
,JsonObject
。
- 子类:
JsonObject
:内部使用std::map<std::string, JsonNode*>
存储键值对。JsonArray
:内部使用std::vector<JsonNode*>
存储元素。
- 内存管理:
- 使用智能指针(
std::unique_ptr
或std::shared_ptr
)避免手动内存释放。 - 递归销毁 JSON 树结构。
- 使用智能指针(
- 核心类:
解析流程
- 词法分析(Lexical Analysis):
- 将输入字符串拆分为 Token(如
"{"
,"name"
,":"
,"Alice"
)。 - 支持跳过空白字符,处理转义字符。
- 将输入字符串拆分为 Token(如
- 语法分析(Syntax Analysis):
- 使用递归下降解析(Recursive Descent Parsing)方法。
- 根据 Token 类型构建
JsonNode
树结构。
- 错误处理:
- 定义异常类(如
JsonParseException
),在解析失败时抛出。
- 定义异常类(如
- 词法分析(Lexical Analysis):
序列化流程
- 递归遍历:
- 根据
JsonNode
的类型生成对应的字符串。 - 对象需遍历所有键值对,数组需遍历所有元素。
- 根据
- 格式化输出:
- 可选参数控制缩进(如每级缩进 2 个空格)。
- 使用
std::ostringstream
构建最终字符串。
- 递归遍历:
关键函数设计
- 解析接口:
class JsonParser { public: static JsonNode* parse(const std::string& jsonStr); };
- 序列化接口:
class JsonSerializer { public: static std::string serialize(const JsonNode* node, int indentLevel = 0); };
- 解析接口:
测试与验证
- 编写单元测试验证解析器对各种 JSON 结构的支持(如嵌套、转义字符)。
- 使用已知的 JSON 测试用例(如 JSONTestSuite)。
四、代码实现
1. 数据结构设计
#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <memory>
#include <stdexcept>
// 基类:JSON节点
class JsonNode {
public:
virtual ~JsonNode() = default;
virtual std::string toString(int indent = 0) const = 0; // 序列化接口
};
// 子类:字符串
class JsonString : public JsonNode {
private:
std::string value;
public:
JsonString(const std::string& str) : value(str) {}
std::string toString(int indent = 0) const override {
return "\"" + value + "\"";
}
};
// 子类:数字
class JsonNumber : public JsonNode {
private:
double value;
public:
JsonNumber(double num) : value(num) {}
std::string toString(int indent = 0) const override {
return std::to_string(value);
}
};
// 子类:布尔值
class JsonBoolean : public JsonNode {
private:
bool value;
public:
JsonBoolean(bool val) : value(val) {}
std::string toString(int indent = 0) const override {
return value ? "true" : "false";
}
};
// 子类:空值
class JsonNull : public JsonNode {
public:
std::string toString(int indent = 0) const override {
return "null";
}
};
// 子类:数组
class JsonArray : public JsonNode {
private:
std::vector<std::shared_ptr<JsonNode>> elements;
public:
void add(std::shared_ptr<JsonNode> node) {
elements.push_back(node);
}
std::string toString(int indent = 0) const override {
std::string result = "[\n";
for (size_t i = 0; i < elements.size(); ++i) {
result += std::string(indent + 4, ' ') + elements[i]->toString(indent + 4);
if (i != elements.size() - 1) result += ",";
result += "\n";
}
result += std::string(indent, ' ') + "]";
return result;
}
};
// 子类:对象
class JsonObject : public JsonNode {
private:
std::map<std::string, std::shared_ptr<JsonNode>> members;
public:
void insert(const std::string& key, std::shared_ptr<JsonNode> node) {
members[key] = node;
}
std::string toString(int indent = 0) const override {
std::string result = "{\n";
for (const auto& [key, value] : members) {
result += std::string(indent + 4, ' ') + "\"" + key + "\": " + value->toString(indent + 4);
if (&value != &members.rbegin()->second) result += ",";
result += "\n";
}
result += std::string(indent, ' ') + "}";
return result;
}
};
2. 解析器实现
class JsonParser {
public:
// 主解析入口
static std::shared_ptr<JsonNode> parse(const std::string& jsonStr) {
std::size_t pos = 0;
return parseValue(jsonStr, pos);
}
private:
// 解析值(递归下降)
static std::shared_ptr<JsonNode> parseValue(const std::string& str, std::size_t& pos) {
skipWhitespace(str, pos);
if (str[pos] == '{') return parseObject(str, pos);
if (str[pos] == '[') return parseArray(str, pos);
if (str[pos] == '"') return parseString(str, pos);
if (str[pos] == 't' || str[pos] == 'f') return parseBoolean(str, pos);
if (str[pos] == 'n') return parseNull(str, pos);
return parseNumber(str, pos);
}
// 解析对象
static std::shared_ptr<JsonNode> parseObject(const std::string& str, std::size_t& pos) {
auto obj = std::make_shared<JsonObject>();
++pos; // 跳过 '{'
skipWhitespace(str, pos);
while (str[pos] != '}') {
auto key = parseString(str, pos)->toString().substr(1, std::string::npos - 2);
skipWhitespace(str, ++pos); // 跳过冒号
auto value = parseValue(str, pos);
obj->insert(key, value);
skipWhitespace(str, ++pos); // 跳过逗号或 '}'
if (str[pos] == ',') {
++pos; // 跳过逗号
skipWhitespace(str, ++pos);
}
}
++pos; // 跳过 '}'
return obj;
}
// 解析数组
static std::shared_ptr<JsonNode> parseArray(const std::string& str, std::size_t& pos) {
auto arr = std::make_shared<JsonArray>();
++pos; // 跳过 '['
skipWhitespace(str, pos);
while (str[pos] != ']') {
auto value = parseValue(str, pos);
arr->add(value);
skipWhitespace(str, ++pos); // 跳过逗号或']'
if (str[pos] == ',') {
++pos; // 跳过逗号
skipWhitespace(str, ++pos);
}
}
++pos; // 跳过 ']'
return arr;
}
// 解析字符串
static std::shared_ptr<JsonNode> parseString(const std::string& str, std::size_t& pos) {
++pos; // 跳过开头引号
std::string result;
while (str[pos] != '"') {
if (str[pos] == '\\') {
++pos; // 处理转义字符
switch (str[pos]) {
case 'b': result += '\b'; break;
case 'f': result += '\f'; break;
case 'r': result += '\r'; break;
case 'n': result += '\n'; break;
case 't': result += '\t'; break;
case '"': result += '"'; break;
case '\\': result += '\\'; break;
default: throw std::runtime_error("未知转义字符");
}
} else {
result += str[pos];
}
++pos;
}
++pos; // 跳过结尾引号
return std::make_shared<JsonString>(result);
}
// 解析布尔值
static std::shared_ptr<JsonNode> parseBoolean(const std::string& str, std::size_t& pos) {
if (str.compare(pos, 4, "true") == 0) {
pos += 4;
return std::make_shared<JsonBoolean>(true);
}
if (str.compare(pos, 5, "false") == 0) {
pos += 5;
return std::make_shared<JsonBoolean>(false);
}
throw std::runtime_error("无效布尔值");
}
// 解析 null
static std::shared_ptr<JsonNode> parseNull(const std::string& str, std::size_t& pos) {
if (str.compare(pos, 4, "null") == 0) {
pos += 4;
return std::make_shared<JsonNull>();
}
throw std::runtime_error("无效 null 值");
}
// 解析数字
static std::shared_ptr<JsonNode> parseNumber(const std::string& str, std::size_t& pos) {
std::size_t end = pos;
while (isdigit(str[end]) || str[end] == '.' || str[end] == '-' || str[end] == 'e' || str[end] == 'E') {
++end;
}
std::string numStr = str.substr(pos, end - pos);
pos = end;
try {
return std::make_shared<JsonNumber>(std::stod(numStr));
} catch (...) {
throw std::runtime_error("无效数字格式");
}
}
// 跳过空白字符
static void skipWhitespace(const std::string& str, std::size_t& pos) {
while (pos < str.size() && isspace(str[pos])) ++pos;
}
};
3. 序列化器实现
class JsonSerializer {
public:
static std::string serialize(const JsonNode* node, int indentLevel = 0) {
return node->toString(indentLevel);
}
};
五、运行示例
1. 基础用法
int main() {
// 示例 JSON 字符串
std::string jsonStr = R"({
"name": "Alice",
"age": 30,
"isStudent": false,
"hobbies": ["reading", "coding"],
"address": {
"city": "Beijing",
"zip": 100000
}
})";
// 解析 JSON
try {
auto root = JsonParser::parse(jsonStr);
std::cout << "解析后的 JSON 结构:" << std::endl;
std::cout << JsonSerializer::serialize(root.get(), 2) << std::endl;
} catch (const std::exception& e) {
std::cerr << "解析错误: " << e.what() << std::endl;
}
return 0;
}
输出结果:
解析后的 JSON 结构:
{
"name": "Alice",
"age": 30,
"isStudent": false,
"hobbies": [
"reading",
"coding"
],
"address": {
"city": "Beijing",
"zip": 100000
}
}
2. 错误处理示例
std::string invalidJson = "{ \"name\": \"Bob\", \"age\": }"; // 缺少值
try {
auto root = JsonParser::parse(invalidJson);
} catch (const std::exception& e) {
std::cerr << "错误: " << e.what() << std::endl;
}
输出结果:
错误: 无效数字格式
六、调试技巧
非法输入处理:
- 使用
try-catch
捕获异常,定位语法错误(如括号不匹配、类型错误)。 - 在
parseValue
中添加日志输出,记录当前解析位置和字符。
- 使用
内存泄漏检查:
- 使用
valgrind
工具检测内存泄漏。 - 确保所有
std::shared_ptr
正确释放。
- 使用
单元测试:
- 针对不同数据类型编写测试用例(如嵌套对象、数组、转义字符)。
- 使用 Google Test 框架自动化测试。
七、扩展示例
1. 支持注释(非标准 JSON 扩展)
JSON 标准不支持注释,但可以通过解析器扩展实现类似 C/C++ 的注释语法(//
和 /* */
)。
实现思路:
- 在词法分析阶段跳过注释内容。
- 修改
skipWhitespace
函数,增加对注释的处理逻辑。
// 修改后的 skipWhitespace 函数
static void skipWhitespace(const std::string& str, std::size_t& pos) {
while (pos < str.size()) {
if (isspace(str[pos])) ++pos;
else if (str[pos] == '/' && pos + 1 < str.size()) {
if (str[pos + 1] == '/') { // 行注释
pos = str.find('\n', pos);
if (pos != std::string::npos) ++pos; // 跳过换行符
} else if (str[pos + 1] == '*') { // 块注释
pos += 2;
while (pos < str.size() && !(str[pos] == '*' && pos + 1 < str.size() && str[pos + 1] == '/')) {
++pos;
}
if (pos + 1 < str.size()) pos += 2; // 跳过 "*/"
} else {
break; // 非注释斜杠
}
} else {
break;
}
}
}
示例输入:
{
"name": "Bob", // 用户名
"age": 25, /* 年龄字段 */
"hobbies": ["coding", "gaming"]
}
2. 支持文件读写
将 JSON 解析器与文件系统结合,实现从文件读取和写入 JSON 数据。
代码实现:
#include <fstream>
// 从文件读取 JSON
std::shared_ptr<JsonNode> parseFromFile(const std::string& filename) {
std::ifstream file(filename);
if (!file.is_open()) throw std::runtime_error("无法打开文件");
std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
return JsonParser::parse(content);
}
// 将 JSON 写入文件
void writeToFile(const JsonNode* node, const std::string& filename) {
std::ofstream file(filename);
if (!file.is_open()) throw std::runtime_error("无法写入文件");
file << JsonSerializer::serialize(node);
}
使用示例:
int main() {
auto json = parseFromFile("data.json");
writeToFile(json.get(), "output.json");
return 0;
}
3. 性能优化示例
- 减少内存拷贝:
使用std::string_view
替代std::string
存储键和值(需 C++17 支持)。 - 缓存中间结果:
在序列化时缓存字符串拼接结果,避免重复计算。 - 多线程解析:
对大型 JSON 文件,可将解析任务拆分为多个线程(需注意线程安全)。
八、总结
1. 项目成果
- 实现了一个完整的 JSON 解析器,支持基本数据类型和嵌套结构。
- 提供了清晰的接口设计,便于后续扩展(如注释、文件读写)。
- 通过递归下降解析和智能指针管理内存,保证了代码健壮性。
2. 不足与改进方向
- 当前不足:
- 未完全遵循 JSON 规范(如未处理 Unicode 编码、浮点数精度问题)。
- 错误提示不够详细(仅抛出异常,未定位错误位置)。
- 改进方向:
- 增加对 Unicode 字符的完整支持(如
\uXXXX
解码)。 - 添加 JSON Schema 验证功能。
- 支持流式解析(适用于超大 JSON 文件)。
- 增加对 Unicode 字符的完整支持(如
3. 性能优化建议
- 内存管理:
- 使用对象池(Object Pool)复用
JsonNode
对象,减少频繁的内存分配。
- 使用对象池(Object Pool)复用
- 错误处理:
- 提供更细粒度的错误码和位置信息(如行号、列号)。
- 序列化优化:
- 使用
std::ostringstream
替代多次字符串拼接,提升效率。
- 使用
4. 未来扩展方向
- 支持其他格式:
- 添加 XML/YAML 解析器,实现多格式转换。
- 跨平台兼容性:
- 适配不同操作系统(如 Windows/Linux)的文件路径处理。
- 集成主流库:
- 与 Boost.PropertyTree、nlohmann/json 等库对比,提供兼容接口。