C++ 第四阶段项目一:实现 JSON 解析器

发布于:2025-07-05 ⋅ 阅读:(21) ⋅ 点赞:(0)

目录

一、项目目标

二、功能需求

三、实现思路

四、代码实现

1. 数据结构设计

2. 解析器实现

3. 序列化器实现

五、运行示例

1. 基础用法

2. 错误处理示例

六、调试技巧

七、扩展示例

1. 支持注释(非标准 JSON 扩展)

2. 支持文件读写

3. 性能优化示例

八、总结

1. 项目成果

2. 不足与改进方向

3. 性能优化建议

4. 未来扩展方向


一、项目目标
  1. 基础功能
    • 实现 JSON 数据的解析(Parsing)和序列化(Serialization)。
    • 支持 JSON 的基本数据类型:字符串、数字、布尔值、null、数组、对象(键值对集合)。
    • 支持嵌套结构(如数组中嵌套对象,对象中嵌套数组)。
  2. 扩展性
    • 提供清晰的接口,便于后续扩展(如支持注释、二进制格式转换等)。
    • 支持自定义错误处理机制(如语法错误提示、非法输入检测)。
  3. 性能与健壮性
    • 优化内存管理,避免内存泄漏。
    • 处理非法输入(如格式错误、类型不匹配)时提供友好的错误信息。

二、功能需求
  1. JSON 解析(Parsing)

    • 输入:符合 JSON 格式的字符串(如 "{\"name\":\"Alice\",\"age\":25}")。
    • 输出:内存中的 JSON 数据结构(如树形结构或嵌套映射)。
    • 支持语法
      • 基本类型:字符串(带双引号)、数字(整数/浮点数)、布尔值(true/false)、null
      • 复合类型:数组([...])、对象({...})。
      • 转义字符:\n\t\"\\ 等。
    • 错误处理
      • 检测并报告语法错误(如缺少引号、括号不匹配)。
      • 忽略合法的空白字符(空格、换行符、制表符)。
  2. JSON 序列化(Serialization)

    • 输入:内存中的 JSON 数据结构。
    • 输出:符合 JSON 格式的字符串。
    • 格式要求
      • 字符串需用双引号包裹。
      • 数字无需引号,布尔值和 null 使用关键字。
      • 对象的键必须为字符串(带引号)。
      • 支持缩进格式(可选,用于美观化输出)。
  3. 扩展功能(后续阶段)

    • 支持注释(如 // 或 /* */,需标注为非标准 JSON)。
    • 支持从文件读取/写入 JSON 数据。
    • 支持与其他数据格式转换(如 XML、YAML)。

三、实现思路
  1. 数据结构设计

    • 核心类
      • JsonNode:表示 JSON 的任意节点,支持多态(继承自基类)。
        • 子类:JsonStringJsonNumberJsonBooleanJsonNullJsonArrayJsonObject
      • JsonObject:内部使用 std::map<std::string, JsonNode*> 存储键值对。
      • JsonArray:内部使用 std::vector<JsonNode*> 存储元素。
    • 内存管理
      • 使用智能指针(std::unique_ptr 或 std::shared_ptr)避免手动内存释放。
      • 递归销毁 JSON 树结构。
  2. 解析流程

    • 词法分析(Lexical Analysis)
      • 将输入字符串拆分为 Token(如 "{""name"":""Alice")。
      • 支持跳过空白字符,处理转义字符。
    • 语法分析(Syntax Analysis)
      • 使用递归下降解析(Recursive Descent Parsing)方法。
      • 根据 Token 类型构建 JsonNode 树结构。
    • 错误处理
      • 定义异常类(如 JsonParseException),在解析失败时抛出。
  3. 序列化流程

    • 递归遍历
      • 根据 JsonNode 的类型生成对应的字符串。
      • 对象需遍历所有键值对,数组需遍历所有元素。
    • 格式化输出
      • 可选参数控制缩进(如每级缩进 2 个空格)。
      • 使用 std::ostringstream 构建最终字符串。
  4. 关键函数设计

    • 解析接口
      class JsonParser {
      public:
          static JsonNode* parse(const std::string& jsonStr);
      };
    • 序列化接口
      class JsonSerializer {
      public:
          static std::string serialize(const JsonNode* node, int indentLevel = 0);
      };
  5. 测试与验证

    • 编写单元测试验证解析器对各种 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;
}

输出结果

错误: 无效数字格式

六、调试技巧
  1. 非法输入处理

    • 使用 try-catch 捕获异常,定位语法错误(如括号不匹配、类型错误)。
    • 在 parseValue 中添加日志输出,记录当前解析位置和字符。
  2. 内存泄漏检查

    • 使用 valgrind 工具检测内存泄漏。
    • 确保所有 std::shared_ptr 正确释放。
  3. 单元测试

    • 针对不同数据类型编写测试用例(如嵌套对象、数组、转义字符)。
    • 使用 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 文件)。
3. 性能优化建议
  • 内存管理
    • 使用对象池(Object Pool)复用 JsonNode 对象,减少频繁的内存分配。
  • 错误处理
    • 提供更细粒度的错误码和位置信息(如行号、列号)。
  • 序列化优化
    • 使用 std::ostringstream 替代多次字符串拼接,提升效率。
4. 未来扩展方向
  • 支持其他格式
    • 添加 XML/YAML 解析器,实现多格式转换。
  • 跨平台兼容性
    • 适配不同操作系统(如 Windows/Linux)的文件路径处理。
  • 集成主流库
    • 与 Boost.PropertyTree、nlohmann/json 等库对比,提供兼容接口。

网站公告

今日签到

点亮在社区的每一天
去签到