C++JSON 解析器

本文记录了一个纯 C++17 JSON 解析器的完整实现过程,涵盖词法分析、递归下降解析、Unicode 处理、整数精度保证、序列化策略等核心技术点。全部代码约 1500 行,无任何第三方依赖,符合 RFC 8259 标准。


一、为什么手写 JSON 解析器?

在开始之前,项目中已经集成了 jsoncpp。对于大多数项目来说,这足够了------jsoncpp 稳定、经过充分测试、功能完整。

但作为一个 C++ 开发者,理解解析器的内部原理,和直接调用库 API,是完全不同的两个层次。手写一遍,你会获得:

  • 递归下降解析 的深入理解:每个 parseValue() 调用都在栈上展开一棵语法树
  • 编码细节的掌控:UTF-8、Unicode surrogate pair、转义序列的处理
  • 浮点数精度 的认识:为什么 9223372036854775807 会变成 9.22337e+18
  • C++17 特性 的实践:copy-and-swap、结构化绑定、[[noreturn]]

而且,没有外部依赖的解析器,在嵌入式、游戏引擎、编辑器插件等场景下,有独特的优势。


二、整体架构

解析器分为 4 个模块,按数据流方向排列:

复制代码
输入字符串/流 ──→ Source ──→ Lexer ──→ Parser ──→ JsonValue
                        ↑           ↑           ↑
                   字符源抽象    字符→Token    Token→语法树

模块划分:

模块 职责 关键类
JsonValue 值存储、类型安全访问、序列化 JsonValue, JsonParseException
JsonLexer 词法分析、Source 抽象 Lexer, Source, Token
JsonParser 语法分析、递归下降 Parser

三、JsonValue:类型安全的值存储

3.1 标记联合体设计

JSON 有 7 种数据类型:nullboolintdoublestringarrayobject。在 C++ 中,我们需要一个可区分联合体(tagged union)。

天真的想法 :用 std::variant

cpp 复制代码
using JsonVariant = std::variant<
    std::nullptr_t, bool, int64_t, double,
    std::string,
    std::vector<JsonValue>,               // error: incomplete type
    std::map<std::string, JsonValue>      // error: incomplete type
>;

编译失败。std::variant 要求所有 alternative 是完整类型 ,但 vector<JsonValue> 中的 JsonValue 还在定义中。这就是 C++ 中经典的自引用类型循环依赖。

解决方案 :用 std::unique_ptr 打破循环。

cpp 复制代码
class JsonValue {
    struct Array : public std::vector<JsonValue> {};
    struct Object : public std::map<std::string, JsonValue> {};

    JsonType m_type;              // 标记
    bool      m_bool;             // 直接存储标量
    int64_t   m_int;
    double    m_double;
    std::string m_string;
    std::unique_ptr<Array>  m_array;   // 递归类型用指针
    std::unique_ptr<Object> m_object;
};

unique_ptr 只需要前置声明,不要求完整类型。在构造函数中才 new Array(...),此时 JsonValue 已完整定义。析构时 unique_ptr 自动释放。

这 7 个成员中,同一时刻只有 1-2 个有值。现代 C++ 中可以用 union 优化,但 unique_ptr 方案代码更清晰,且对齐开销可以接受。

3.2 copy-and-swap

赋值运算符采用 copy-and-swap 惯用法:

cpp 复制代码
JsonValue& operator=(JsonValue other) {  // 传值:拷贝或移动至此
    swap(*this, other);                   // 交换成员
    return *this;                         // 旧数据随 other 析构释放
}

这提供了强异常安全保证:要么赋值完全成功,要么对象状态不变。而且同时覆盖了拷贝赋值和移动赋值,只需一份代码。

3.3 类型安全访问

每个 asXxx() 方法都做运行时类型检查:

cpp 复制代码
int64_t asInt() const {
    if (m_type == JsonType::Int) return m_int;
    if (m_type == JsonType::Double) return static_cast<int64_t>(m_double);
    throwTypeError(JsonType::Int);  // [[noreturn]]
}

asInt() 允许 Double → Int 截断,但 boolstring 调用 asInt() 会抛异常。这种宽容输入、严格输出的设计,让 JSON 的弱类型映射到 C++ 的强类型时,既有灵活性又有安全性。

3.4 operator\[\] 的 const 重载

const operator[] 在 key/index 不存在时返回静态 null 引用,而非抛异常。这使得链式访问成为可能:

cpp 复制代码
std::string name = doc["config"]["nested"]["name"].asString();
//                  即使中间某层不存在,也只是链式返回 null
//                  null.asString() → 抛异常

需要严格检查时,用 at()contains()

cpp 复制代码
if (doc.contains("config") && doc["config"].isObject()) {
    const auto& val = doc.at("config"); // 确定存在后再用 at
}

四、Lexer:词法分析器

4.1 Source 抽象层

词法分析的第一步是读取字符 。最简单的方案是将整个输入加载到 std::string 中,但这对于大文件不友好。

方案:设计 Source 抽象基类:

cpp 复制代码
class Source {
public:
    virtual char peek(size_t offset = 0) = 0;  // 前瞻 n 个字符
    virtual char advance() = 0;                 // 消费一个字符
    virtual size_t line() const = 0;            // 当前行号
    virtual size_t col() const = 0;             // 当前列号
};

两个实现:

实现 输入 适用场景
StringSource const std::string& 内存字符串
StreamSource std::istream& 文件/stdin/网络流

StreamSource 内部维护前瞻缓冲区,按需从流中读取,避免预加载整个文件:

cpp 复制代码
class StreamSource : public Source {
    std::istream& m_is;
    std::string m_buf;    // 前瞻缓冲区
    size_t m_bufPos = 0;  // 缓冲区内位置
    // ...
    void fill(size_t need) {
        while (!m_eof && m_buf.size() < m_bufPos + need + 1) {
            char c;
            if (m_is.get(c)) m_buf += c;
            else { m_eof = true; break; }
        }
    }
};

4.2 Token 流

Lexer 是前瞻一个 Token 的流式词法分析器:

cpp 复制代码
class Lexer {
public:
    Lexer(Source& src);    // 构造时立即预读第一个 Token
    const Token& peek();   // 查看当前 Token(不消费)
    Token consume();       // 消费当前 Token,前进到下一个
};

Token 结构:

cpp 复制代码
struct Token {
    TokenType type;         // 类型
    std::string stringVal;  // 字符串值
    double      numberVal;  // 浮点数路径
    int64_t     intVal;     // 整数路径(完整 64 位)
    bool        isFromFloat;// 是否是浮点原文
    size_t line, col;       // 位置
};

这里有一个关键设计 :整数和浮点数用两个成员分开存储。intVal 保证整数的完整 64 位精度,numberVal 只在原文含 .e/E 时才使用。后面会详述为什么这很重要。

4.3 字符串转义与 Unicode

JSON 字符串支持 \uXXXX 转义。我们的 Lexer 支持完整的 Unicode 处理:

  1. 解析 \uXXXX 为 16 位码点
  2. 检测 surrogate pair:\uD800-\uDBFF(高代理) + \uDC00-\uDFFF(低代理)
  3. 将码点编码为 UTF-8(1-4 字节)
cpp 复制代码
std::string Lexer::readUnicodeEscape(size_t ln, size_t cl) {
    uint32_t cp = 0;
    for (int i = 0; i < 4; ++i) { /* 解析 4 位十六进制 */ }

    if (cp >= 0xD800 && cp <= 0xDBFF) {
        // 高代理:继续解析低代理
        /* ... */
        cp = 0x10000 + ((cp - 0xD800) << 10) + (low - 0xDC00);
    }

    return codepointToUTF8(cp);  // 转为 UTF-8 字节序列
}

这意味着 🍎(🍎 的 surrogate pair)能正确解析为 UTF-8 的 F0 9F 8D 8E


五、Parser:递归下降解析

5.1 文法

ebnf 复制代码
value     = null | false | true | number | string | array | object
array     = '[' ( value (',' value)* )? ']'
object    = '{' ( string ':' value (',' string ':' value)* )? '}'

5.2 实现模式

每个文法产生式对应一个解析函数,返回 JsonValue

cpp 复制代码
JsonValue parseValue() {
    switch (m_lexer.peek().type) {
        case TokenType::Null:  /* ... */ return JsonValue(nullptr);
        case TokenType::True:  /* ... */ return JsonValue(true);
        case TokenType::False: /* ... */ return JsonValue(false);
        case TokenType::Number: return parseNumber();
        case TokenType::String: return parseStringToken();
        case TokenType::Lbracket: return parseArray();
        case TokenType::Lbrace:   return parseObject();
    }
}

parseArray 的实现:

cpp 复制代码
JsonValue parseArray() {
    m_lexer.expect(TokenType::Lbracket);
    JsonValue::Array arr;

    if (m_lexer.peek().type == TokenType::Rbracket) {
        m_lexer.consume();
        return JsonValue(std::move(arr));  // 空数组快速路径
    }

    arr.push_back(parseValue());
    while (m_lexer.peek().type == TokenType::Comma) {
        m_lexer.consume();
        arr.push_back(parseValue());
    }

    m_lexer.expect(TokenType::Rbracket);
    return JsonValue(std::move(arr));
}

5.3 整数精度保证

这是一个容易踩坑的点。看这段代码:

cpp 复制代码
// 错误做法
int64_t i = std::stoll("9223372036854775807");
double  d = static_cast<double>(i);  // 精度丢失!
// d 变成了 9223372036854775808.0(偏 1)

double 的尾数只有 53 位,约 15-16 位十进制精度。int64_t 有 63 位有效位,约 19 位十进制精度。任何 ≥2⁵³ 的整数在 double 中都无法精确表示。

解决方案:Lexer 跟踪数字原文的书写形式,Parser 据此决策:

cpp 复制代码
// Lexer
Token readNumber(/* ... */) {
    bool isFloat = false;
    /* 解析 '.' → isFloat = true */
    /* 解析 'e'/'E' → isFloat = true */
    Token t(TokenType::Number, ln, cl);
    t.isFromFloat = isFloat;
    if (isFloat) t.numberVal = std::stod(numStr);  // 浮点数路径
    else         t.intVal    = std::stoll(numStr);  // 整数路径(完整精度)
    return t;
}

// Parser
JsonValue parseNumber() {
    Token t = m_lexer.consume();
    if (t.isFromFloat) return JsonValue(t.numberVal);  // Double
    return JsonValue(t.intVal);                         // Int
}

这样 9223372036854775807 保持为 int64_t 存储,序列化时也输出为整数。


六、序列化策略

6.1 双精度输出

双精度序列化面临一个经典问题:3.14 在 IEEE 754 中无法精确表示,实际存储值约为 3.1400000000000001...。如果直接用 std::ostream::precision(17) 输出,会得到:

复制代码
3.1400000000000001  ← 丑,且不是最短表示

我们的策略:

cpp 复制代码
tmp.precision(15);  // 15 位有效数字,消除二进制噪声
tmp << d;           // → "3.1400000000000001" 变为 "3.14"
// 尾零裁剪
auto last = mant.find_last_not_of('0');
if (last > dot) mant.erase(last + 1);
// 保证 ".0" 后缀
// 科学记数法处理 "1e+20" → "1.0e+20"

precision(15) 替代 precision(17) 背后的原理:double 有 53 位尾数,对应约 15.95 位十进制精度。实际保证的往返(round-trip)精度是 15 位,第 16-17 位是二进制表示截断产生的噪声。

6.2 美化输出

美化输出采用递归缩进策略:

  • 空数组/对象输出 [] / {}(无换行)
  • 非空数组每行一个元素
  • 非空对象每行一个键值对
  • 缩进级别由参数控制(默认 2 空格)

七、错误处理

解析错误携带精确的行号和列号。错误由 JsonParseException 报告:

cpp 复制代码
class JsonParseException : public std::exception {
    size_t m_line, m_col;
    std::string m_what;  // "At line 3, column 10: Expected '}', got ','"
};

Lexer 和 Parser 在检测到异常时立即抛出。调用栈展开后,用户看到的是人类可读的错误定位


八、重构与设计审查

完成基础实现后,我们进行了一次全面的设计审查,发现并修复了若干问题:

问题 严重度 根因 修复
大整数精度丢失 🔴 P0 double 中转整数 添加 intVal 字段,完全绕过 double
数字类型误判 🟠 P1 double→int64_t→double 比较策略 isFromFloat 标记+书写形式判定
double 精度噪声 🟠 P1 precision(17) 输出太长 改为 precision(15) + 尾零裁剪
科学记数法后缀错误 🟠 P1 1e+201e+20.0 识别指数位,在指数前加 .0

审查的另一个重要产出是代码解耦:从单文件(1100 行)拆分为 4 个独立模块,每文件 100-300 行,降低了认知负载。


九、总结

写一个 JSON 解析器,让我意识到几个事情:

  1. RFC 8259 看似简单,细节不少。字符串转义、Unicode surrogate pair、数字格式的边界情况,都需要仔细处理。
  2. 浮点数精度是暗坑 。用 double 中转整数的做法在超过 2⁵³ 时悄无声息地丢失精度。这个 bug 在单元测试中很容易被忽略。
  3. 设计审查的价值。单文件工作时觉得一切都好,回头看才发现 6 个 P0/P1 级别的问题。代码解耦也让模块间的接口更清晰。
  4. C++17 是个好工具 。copy-and-swap、结构化绑定、unique_ptr[[noreturn]] 等特性的组合,让手写解析器的代码量保持在合理范围内。

如果你也想挑战自己,我建议你也手写一个------不需要很复杂,支持 JSON 子集即可。解析器的实现过程,是理解编译原理的最小可行实践


十、附录:完整源代码

以下为项目全部源代码,按模块划分。编译方式:

bash 复制代码
# VS 2022 (MSBuild)
msbuild JSONParser.vcxproj /p:Configuration=Debug /p:Platform=x64

# GCC / Clang
g++ -std=c++17 -O2 -Wall -Wextra main.cpp json/JsonValue.cpp json/JsonLexer.cpp json/JsonParser.cpp -o json_parser

10.1 json/JsonValue.h

cpp 复制代码
#pragma once
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include <map>
#include <memory>
#include <cstdint>
#include <cstdlib>
#include <cmath>
#include <stdexcept>
#include <algorithm>

// ============================================================
//  Type enumeration
// ============================================================
enum class JsonType {
    Null, Bool, Int, Double, String, Array, Object
};

inline const char* jsonTypeName(JsonType t) {
    switch (t) {
        case JsonType::Null:   return "null";
        case JsonType::Bool:   return "bool";
        case JsonType::Int:    return "int";
        case JsonType::Double: return "double";
        case JsonType::String: return "string";
        case JsonType::Array:  return "array";
        case JsonType::Object: return "object";
    }
    return "unknown";
}

// ============================================================
//  Exception class
// ============================================================
class JsonParseException : public std::exception {
public:
    JsonParseException(size_t line, size_t col, const std::string& msg)
        : m_line(line), m_col(col)
    {
        std::ostringstream oss;
        oss << "At line " << line << ", column " << col << ": " << msg;
        m_what = oss.str();
    }

    const char* what() const noexcept override { return m_what.c_str(); }
    size_t line()   const noexcept { return m_line; }
    size_t column() const noexcept { return m_col; }

private:
    size_t m_line, m_col;
    std::string m_what;
};

// Forward declarations
class Parser;

// ============================================================
//  JsonValue -- Core variant-based JSON value
// ============================================================
class JsonValue {
public:
    struct Array : public std::vector<JsonValue> {
        using std::vector<JsonValue>::vector;
    };
    struct Object : public std::map<std::string, JsonValue> {
        using std::map<std::string, JsonValue>::map;
    };

    // ---- Constructors ----
    JsonValue() : m_type(JsonType::Null) {}
    JsonValue(std::nullptr_t) : m_type(JsonType::Null) {}
    JsonValue(bool v) : m_type(JsonType::Bool), m_bool(v) {}
    JsonValue(int v) : m_type(JsonType::Int), m_int(static_cast<int64_t>(v)) {}
    JsonValue(long v) : m_type(JsonType::Int), m_int(static_cast<int64_t>(v)) {}
    JsonValue(long long v) : m_type(JsonType::Int), m_int(static_cast<int64_t>(v)) {}
    JsonValue(unsigned long v) : m_type(JsonType::Int), m_int(static_cast<int64_t>(v)) {}
    JsonValue(unsigned long long v)
        : m_type(JsonType::Int), m_int(static_cast<int64_t>(v)) {}
    JsonValue(double v) : m_type(JsonType::Double), m_double(v) {}
    JsonValue(const char* v) : m_type(JsonType::String), m_string(v) {}
    JsonValue(const std::string& v) : m_type(JsonType::String), m_string(v) {}
    JsonValue(std::string&& v) noexcept
        : m_type(JsonType::String), m_string(std::move(v)) {}
    JsonValue(const Array& arr)
        : m_type(JsonType::Array), m_array(new Array(arr)) {}
    JsonValue(Array&& arr) noexcept
        : m_type(JsonType::Array), m_array(new Array(std::move(arr))) {}
    JsonValue(const Object& obj)
        : m_type(JsonType::Object), m_object(new Object(obj)) {}
    JsonValue(Object&& obj) noexcept
        : m_type(JsonType::Object), m_object(new Object(std::move(obj))) {}

    // ---- Copy / Move / Destroy ----
    JsonValue(const JsonValue& other);
    JsonValue(JsonValue&& other) noexcept;
    JsonValue& operator=(JsonValue other);
    friend void swap(JsonValue& a, JsonValue& b) noexcept;
    ~JsonValue() = default;

    // ---- Type queries ----
    JsonType type() const noexcept { return m_type; }
    bool isNull()   const noexcept { return m_type == JsonType::Null; }
    bool isBool()   const noexcept { return m_type == JsonType::Bool; }
    bool isInt()    const noexcept { return m_type == JsonType::Int; }
    bool isDouble() const noexcept { return m_type == JsonType::Double; }
    bool isString() const noexcept { return m_type == JsonType::String; }
    bool isArray()  const noexcept { return m_type == JsonType::Array; }
    bool isObject() const noexcept { return m_type == JsonType::Object; }
    bool isNumber() const noexcept { return isInt() || isDouble(); }

    // ---- Value access (checked) ----
    bool asBool() const;
    int64_t asInt() const;
    double asDouble() const;
    const std::string& asString() const;
    const Array& asArray() const;
    const Object& asObject() const;
    Array& arrayRef();
    Object& objectRef();

    // ---- Array/Object element access ----
    JsonValue& operator[](size_t idx);
    const JsonValue& operator[](size_t idx) const;
    JsonValue& operator[](const std::string& key);
    JsonValue& operator[](const char* key);
    const JsonValue& operator[](const std::string& key) const;
    const JsonValue& operator[](const char* key) const;

    // ---- Checked access with exceptions ----
    JsonValue& at(size_t idx);
    const JsonValue& at(size_t idx) const;
    JsonValue& at(const std::string& key);
    const JsonValue& at(const std::string& key) const;

    // ---- JSON Pointer (RFC 6901) query ----
    JsonValue query(const std::string& pointer) const;

    // ---- Container queries ----
    bool contains(const std::string& key) const;
    size_t size() const;
    bool empty() const;

    // ---- Standard container typedefs ----
    using value_type      = JsonValue;
    using reference       = JsonValue&;
    using const_reference = const JsonValue&;
    using pointer         = JsonValue*;
    using const_pointer   = const JsonValue*;

    // ---- Iteration ----
    using iterator               = std::vector<JsonValue>::iterator;
    using const_iterator         = std::vector<JsonValue>::const_iterator;

    iterator begin();
    iterator end();
    const_iterator begin() const;
    const_iterator end() const;

    // ---- Object key list ----
    std::vector<std::string> keys() const;
    void keys(std::vector<std::string>& out) const;

    // ---- Serialization ----
    std::string toCompactString() const;
    std::string toPrettyString(int indentStep = 2) const;

    // ---- Comparison ----
    bool operator==(const JsonValue& other) const;
    bool operator!=(const JsonValue& other) const;
    bool operator<(const JsonValue& other) const;
    bool operator<=(const JsonValue& other) const;
    bool operator> (const JsonValue& other) const;
    bool operator>=(const JsonValue& other) const;

private:
    friend class Parser;

    JsonType m_type = JsonType::Null;
    bool      m_bool   = false;
    int64_t   m_int    = 0;
    double    m_double = 0.0;
    std::string m_string;
    std::unique_ptr<Array>  m_array;
    std::unique_ptr<Object> m_object;

    static const JsonValue& nullSingleton() {
        static const JsonValue s_null;
        return s_null;
    }

    void checkType(JsonType expected) const;
    [[noreturn]] void throwTypeError(JsonType expected) const;
    void ensureArray();
    void ensureObject();

    void serializeCompact(std::ostringstream& os) const;
    void serializePretty(std::ostringstream& os, int indent, int step) const;
    static void serializeDouble(std::ostringstream& os, double d);
    static std::string escapeString(const std::string& s);
};

10.2 json/JsonValue.cpp

cpp 复制代码
#include "JsonValue.h"
#include <cstdio>

// ============================================================
//  Copy / Move / Swap
// ============================================================
JsonValue::JsonValue(const JsonValue& other)
    : m_type(other.m_type), m_bool(other.m_bool), m_int(other.m_int),
      m_double(other.m_double), m_string(other.m_string)
{
    if (other.m_array)  m_array.reset(new Array(*other.m_array));
    if (other.m_object) m_object.reset(new Object(*other.m_object));
}

JsonValue::JsonValue(JsonValue&& other) noexcept
    : m_type(other.m_type), m_bool(other.m_bool), m_int(other.m_int),
      m_double(other.m_double), m_string(std::move(other.m_string)),
      m_array(std::move(other.m_array)), m_object(std::move(other.m_object))
{
    other.m_type = JsonType::Null;
}

JsonValue& JsonValue::operator=(JsonValue other) {
    swap(*this, other);
    return *this;
}

void swap(JsonValue& a, JsonValue& b) noexcept {
    using std::swap;
    swap(a.m_type,   b.m_type);
    swap(a.m_bool,   b.m_bool);
    swap(a.m_int,    b.m_int);
    swap(a.m_double, b.m_double);
    swap(a.m_string, b.m_string);
    swap(a.m_array,  b.m_array);
    swap(a.m_object, b.m_object);
}

// ============================================================
//  Value access
// ============================================================
bool JsonValue::asBool() const {
    checkType(JsonType::Bool);
    return m_bool;
}

int64_t JsonValue::asInt() const {
    if (m_type == JsonType::Int) return m_int;
    if (m_type == JsonType::Double) return static_cast<int64_t>(m_double);
    throwTypeError(JsonType::Int);
    return 0;
}

double JsonValue::asDouble() const {
    if (m_type == JsonType::Double) return m_double;
    if (m_type == JsonType::Int) return static_cast<double>(m_int);
    throwTypeError(JsonType::Double);
    return 0.0;
}

const std::string& JsonValue::asString() const {
    checkType(JsonType::String);
    return m_string;
}

const JsonValue::Array& JsonValue::asArray() const {
    checkType(JsonType::Array);
    return *m_array;
}

const JsonValue::Object& JsonValue::asObject() const {
    checkType(JsonType::Object);
    return *m_object;
}

JsonValue::Array& JsonValue::arrayRef() {
    ensureArray();
    return *m_array;
}

JsonValue::Object& JsonValue::objectRef() {
    ensureObject();
    return *m_object;
}

// ============================================================
//  operator[]
// ============================================================
JsonValue& JsonValue::operator[](size_t idx) {
    ensureArray();
    if (idx >= m_array->size()) m_array->resize(idx + 1);
    return (*m_array)[idx];
}

const JsonValue& JsonValue::operator[](size_t idx) const {
    if (!isArray()) return nullSingleton();
    if (idx >= m_array->size()) return nullSingleton();
    return (*m_array)[idx];
}

JsonValue& JsonValue::operator[](const std::string& key) {
    ensureObject();
    return (*m_object)[key];
}

JsonValue& JsonValue::operator[](const char* key) {
    return (*this)[std::string(key)];
}

const JsonValue& JsonValue::operator[](const std::string& key) const {
    if (!isObject()) return nullSingleton();
    auto it = m_object->find(key);
    if (it == m_object->end()) return nullSingleton();
    return it->second;
}

const JsonValue& JsonValue::operator[](const char* key) const {
    return (*this)[std::string(key)];
}

// ============================================================
//  at() - checked access
// ============================================================
JsonValue& JsonValue::at(size_t idx) {
    checkType(JsonType::Array);
    if (idx >= m_array->size())
        throw std::out_of_range("JsonValue::at(): array index out of range");
    return (*m_array)[idx];
}

const JsonValue& JsonValue::at(size_t idx) const {
    checkType(JsonType::Array);
    if (idx >= m_array->size())
        throw std::out_of_range("JsonValue::at(): array index out of range");
    return (*m_array)[idx];
}

JsonValue& JsonValue::at(const std::string& key) {
    checkType(JsonType::Object);
    auto it = m_object->find(key);
    if (it == m_object->end())
        throw std::out_of_range("JsonValue::at(): key \"" + key + "\" not found");
    return it->second;
}

const JsonValue& JsonValue::at(const std::string& key) const {
    checkType(JsonType::Object);
    auto it = m_object->find(key);
    if (it == m_object->end())
        throw std::out_of_range("JsonValue::at(): key \"" + key + "\" not found");
    return it->second;
}

// ============================================================
//  JSON Pointer (RFC 6901)
// ============================================================
JsonValue JsonValue::query(const std::string& pointer) const {
    if (pointer.empty() || pointer == "/")
        return *this;

    if (pointer[0] != '/')
        throw std::invalid_argument("JSON Pointer must start with '/'");

    const JsonValue* current = this;
    size_t pos = 1;

    while (pos < pointer.size()) {
        std::string segment;
        while (pos < pointer.size() && pointer[pos] != '/') {
            if (pointer[pos] == '~' && pos + 1 < pointer.size()) {
                char next = pointer[pos + 1];
                if (next == '0')       { segment += '~'; pos += 2; }
                else if (next == '1')  { segment += '/'; pos += 2; }
                else                   { segment += '~'; pos += 1; }
            } else {
                segment += pointer[pos];
                ++pos;
            }
        }
        if (pos < pointer.size()) ++pos;

        if (current->isObject()) {
            auto it = current->m_object->find(segment);
            if (it == current->m_object->end())
                return JsonValue(nullptr);
            current = &it->second;
        } else if (current->isArray()) {
            char* end = 0;
            long idx = std::strtol(segment.c_str(), &end, 10);
            if (*end != '\0' || idx < 0 || static_cast<size_t>(idx) >= current->m_array->size())
                return JsonValue(nullptr);
            current = &(*current->m_array)[static_cast<size_t>(idx)];
        } else {
            return JsonValue(nullptr);
        }
    }
    return *current;
}

// ============================================================
//  Container queries
// ============================================================
bool JsonValue::contains(const std::string& key) const {
    return isObject() && m_object->count(key) > 0;
}

size_t JsonValue::size() const {
    if (isArray())  return m_array->size();
    if (isObject()) return m_object->size();
    if (isString()) return m_string.size();
    return 0;
}

bool JsonValue::empty() const {
    if (isArray())  return m_array->empty();
    if (isObject()) return m_object->empty();
    if (isString()) return m_string.empty();
    return true;
}

// ============================================================
//  Iteration
// ============================================================
JsonValue::iterator JsonValue::begin() {
    ensureArray();
    return m_array->begin();
}

JsonValue::iterator JsonValue::end() {
    ensureArray();
    return m_array->end();
}

JsonValue::const_iterator JsonValue::begin() const {
    static const Array s_empty;
    if (!isArray()) return s_empty.begin();
    return m_array->begin();
}

JsonValue::const_iterator JsonValue::end() const {
    static const Array s_empty;
    if (!isArray()) return s_empty.end();
    return m_array->end();
}

// ============================================================
//  Keys
// ============================================================
std::vector<std::string> JsonValue::keys() const {
    if (!isObject()) return {};
    std::vector<std::string> result;
    result.reserve(m_object->size());
    for (const auto& kv : *m_object)
        result.push_back(kv.first);
    return result;
}

void JsonValue::keys(std::vector<std::string>& out) const {
    out.clear();
    if (!isObject()) return;
    out.reserve(m_object->size());
    for (const auto& kv : *m_object)
        out.push_back(kv.first);
}

// ============================================================
//  Comparison
// ============================================================
bool JsonValue::operator==(const JsonValue& other) const {
    if (m_type != other.m_type) return false;
    switch (m_type) {
        case JsonType::Null:   return true;
        case JsonType::Bool:   return m_bool == other.m_bool;
        case JsonType::Int:    return m_int == other.m_int;
        case JsonType::Double: return m_double == other.m_double;
        case JsonType::String: return m_string == other.m_string;
        case JsonType::Array:  return *m_array == *other.m_array;
        case JsonType::Object: return *m_object == *other.m_object;
    }
    return false;
}

bool JsonValue::operator!=(const JsonValue& other) const { return !(*this == other); }

bool JsonValue::operator<(const JsonValue& other) const {
    if (m_type != other.m_type) return m_type < other.m_type;
    switch (m_type) {
        case JsonType::Null:   return false;
        case JsonType::Bool:   return m_bool < other.m_bool;
        case JsonType::Int:    return m_int < other.m_int;
        case JsonType::Double: return m_double < other.m_double;
        case JsonType::String: return m_string < other.m_string;
        case JsonType::Array:  return *m_array < *other.m_array;
        case JsonType::Object: return *m_object < *other.m_object;
    }
    return false;
}

bool JsonValue::operator<=(const JsonValue& other) const { return !(other < *this); }
bool JsonValue::operator> (const JsonValue& other) const { return  other < *this; }
bool JsonValue::operator>=(const JsonValue& other) const { return !(*this < other); }

// ============================================================
//  Private helpers
// ============================================================
void JsonValue::checkType(JsonType expected) const {
    if (m_type != expected) {
        std::ostringstream oss;
        oss << "Type mismatch: expected " << jsonTypeName(expected)
            << ", got " << jsonTypeName(m_type);
        throw std::runtime_error(oss.str());
    }
}

[[noreturn]] void JsonValue::throwTypeError(JsonType expected) const {
    std::ostringstream oss;
    oss << "Type mismatch: expected " << jsonTypeName(expected)
        << ", got " << jsonTypeName(m_type);
    throw std::runtime_error(oss.str());
}

void JsonValue::ensureArray() {
    if (m_type == JsonType::Null) {
        m_type = JsonType::Array;
        m_array.reset(new Array());
    } else if (m_type != JsonType::Array) {
        throwTypeError(JsonType::Array);
    }
}

void JsonValue::ensureObject() {
    if (m_type == JsonType::Null) {
        m_type = JsonType::Object;
        m_object.reset(new Object());
    } else if (m_type != JsonType::Object) {
        throwTypeError(JsonType::Object);
    }
}

// ============================================================
//  Serialization helpers
// ============================================================
void JsonValue::serializeDouble(std::ostringstream& os, double d) {
    if (std::isnan(d)) { os << "NaN"; return; }
    if (std::isinf(d)) { os << (d > 0 ? "Infinity" : "-Infinity"); return; }
    std::ostringstream tmp;
    tmp.precision(15);
    tmp << d;
    std::string s = tmp.str();

    auto ePos = s.find('e');
    if (ePos == std::string::npos) ePos = s.find('E');
    auto dot = s.find('.');

    if (dot != std::string::npos) {
        std::string mant = (ePos != std::string::npos) ? s.substr(0, ePos) : s;
        std::string exp  = (ePos != std::string::npos) ? s.substr(ePos) : "";
        auto last = mant.find_last_not_of('0');
        if (last > dot) mant.erase(last + 1);
        else mant.erase(dot + 1);
        os << mant << exp;
    } else if (ePos != std::string::npos) {
        os << s.substr(0, ePos) << ".0" << s.substr(ePos);
    } else {
        os << s << ".0";
    }
}

std::string JsonValue::escapeString(const std::string& s) {
    std::ostringstream os;
    for (char c : s) {
        switch (c) {
            case '"':  os << "\\\""; break;
            case '\\': os << "\\\\"; break;
            case '\b': os << "\\b";  break;
            case '\f': os << "\\f";  break;
            case '\n': os << "\\n";  break;
            case '\r': os << "\\r";  break;
            case '\t': os << "\\t";  break;
            default:
                if (static_cast<unsigned char>(c) < 0x20) {
                    char buf[8];
                    std::snprintf(buf, sizeof(buf), "\\u%04x",
                                  static_cast<unsigned char>(c));
                    os << buf;
                } else {
                    os << c;
                }
                break;
        }
    }
    return os.str();
}

void JsonValue::serializeCompact(std::ostringstream& os) const {
    switch (m_type) {
        case JsonType::Null:
            os << "null"; break;
        case JsonType::Bool:
            os << (m_bool ? "true" : "false"); break;
        case JsonType::Int:
            os << m_int; break;
        case JsonType::Double:
            serializeDouble(os, m_double); break;
        case JsonType::String:
            os << '"' << escapeString(m_string) << '"'; break;
        case JsonType::Array:
            os << '[';
            for (size_t i = 0; i < m_array->size(); ++i) {
                if (i > 0) os << ',';
                (*m_array)[i].serializeCompact(os);
            }
            os << ']';
            break;
        case JsonType::Object:
            os << '{';
            {
                bool first = true;
                for (const auto& kv : *m_object) {
                    if (!first) os << ',';
                    os << '"' << escapeString(kv.first) << "\":";
                    kv.second.serializeCompact(os);
                    first = false;
                }
            }
            os << '}';
            break;
    }
}

void JsonValue::serializePretty(std::ostringstream& os, int indent, int step) const {
    std::string pad(indent, ' ');
    std::string pad2(indent + step, ' ');

    switch (m_type) {
        case JsonType::Null:
            os << "null"; break;
        case JsonType::Bool:
            os << (m_bool ? "true" : "false"); break;
        case JsonType::Int:
            os << m_int; break;
        case JsonType::Double:
            serializeDouble(os, m_double); break;
        case JsonType::String:
            os << '"' << escapeString(m_string) << '"'; break;
        case JsonType::Array:
            if (m_array->empty()) { os << "[]"; }
            else {
                os << "[\n";
                for (size_t i = 0; i < m_array->size(); ++i) {
                    os << pad2;
                    (*m_array)[i].serializePretty(os, indent + step, step);
                    if (i + 1 < m_array->size()) os << ',';
                    os << '\n';
                }
                os << pad << ']';
            }
            break;
        case JsonType::Object:
            if (m_object->empty()) { os << "{}"; }
            else {
                os << "{\n";
                {
                    bool first = true;
                    for (const auto& kv : *m_object) {
                        if (!first) os << ",\n";
                        os << pad2 << '"' << escapeString(kv.first) << "\": ";
                        kv.second.serializePretty(os, indent + step, step);
                        first = false;
                    }
                }
                os << '\n' << pad << '}';
            }
            break;
    }
}

std::string JsonValue::toCompactString() const {
    std::ostringstream os;
    serializeCompact(os);
    return os.str();
}

std::string JsonValue::toPrettyString(int indentStep) const {
    std::ostringstream os;
    serializePretty(os, 0, indentStep);
    return os.str();
}

10.3 json/JsonLexer.h

cpp 复制代码
#pragma once
#include <string>
#include <cstdint>
#include <istream>
#include "JsonValue.h"

// ============================================================
//  Token types
// ============================================================
enum class TokenType {
    Null, True, False,
    Number, String,
    Lbrace, Rbrace,     // { }
    Lbracket, Rbracket, // [ ]
    Colon, Comma,       // : ,
    Eof, Error
};

struct Token {
    TokenType type = TokenType::Error;
    std::string stringVal;
    double      numberVal = 0.0;
    int64_t     intVal = 0;
    bool        isFromFloat = false;
    size_t      line = 1;
    size_t      col  = 1;

    Token() = default;
    Token(TokenType t, size_t ln = 1, size_t cl = 1)
        : type(t), line(ln), col(cl) {}
};

// ============================================================
//  Source abstraction
// ============================================================
class Source {
public:
    virtual ~Source() = default;
    virtual char peek(size_t offset) = 0;
    virtual char advance() = 0;
    virtual size_t line() const = 0;
    virtual size_t col() const = 0;
};

class StringSource : public Source {
    const std::string& m_str;
    size_t m_pos = 0;
    size_t m_line = 1, m_col = 1;
public:
    StringSource(const std::string& s) : m_str(s) {}
    char peek(size_t offset) override {
        size_t idx = m_pos + offset;
        return idx < m_str.size() ? m_str[idx] : '\0';
    }
    char advance() override {
        char c = peek(0);
        if (c == '\0') return '\0';
        ++m_pos;
        if (c == '\n') { ++m_line; m_col = 1; }
        else { ++m_col; }
        return c;
    }
    size_t line() const override { return m_line; }
    size_t col() const override { return m_col; }
};

class StreamSource : public Source {
    std::istream& m_is;
    std::string m_buf;
    size_t m_bufPos = 0;
    size_t m_line = 1, m_col = 1;
    bool m_eof = false;
    void fill(size_t need) {
        while (!m_eof && m_buf.size() < m_bufPos + need + 1) {
            char c;
            if (m_is.get(c)) m_buf += c;
            else { m_eof = true; break; }
        }
    }
public:
    StreamSource(std::istream& is) : m_is(is) {}
    char peek(size_t offset) override {
        fill(offset);
        size_t idx = m_bufPos + offset;
        return idx < m_buf.size() ? m_buf[idx] : '\0';
    }
    char advance() override {
        char c = peek(0);
        if (c == '\0') return '\0';
        ++m_bufPos;
        if (c == '\n') { ++m_line; m_col = 1; }
        else { ++m_col; }
        return c;
    }
    size_t line() const override { return m_line; }
    size_t col() const override { return m_col; }
};

// ============================================================
//  Lexer
// ============================================================
class Lexer {
public:
    Lexer(Source& src);

    const Token& peek() const { return m_next; }
    Token consume();
    bool match(TokenType type);
    void expect(TokenType type);

    static std::string tokenTypeName(TokenType t);

private:
    Source& m_source;
    Token m_next;

    char peekChar(size_t offset = 0) { return m_source.peek(offset); }
    char advanceChar() { return m_source.advance(); }

    void skipWhitespace();
    Token advance();
    Token readString(size_t ln, size_t cl);
    Token readKeyword(const char* expected, TokenType type, size_t ln, size_t cl);
    Token readNumber(size_t ln, size_t cl);
    std::string readUnicodeEscape(size_t ln, size_t cl);
    static std::string codepointToUTF8(uint32_t cp);
};

10.4 json/JsonLexer.cpp

cpp 复制代码
#include "JsonLexer.h"

Lexer::Lexer(Source& src) : m_source(src) {
    m_next = advance();
}

Token Lexer::consume() {
    Token t = m_next;
    m_next = advance();
    return t;
}

bool Lexer::match(TokenType type) {
    if (m_next.type == type) { consume(); return true; }
    return false;
}

void Lexer::expect(TokenType type) {
    if (m_next.type == type) { consume(); return; }
    std::string exp = tokenTypeName(type);
    std::string got = tokenTypeName(m_next.type);
    throw JsonParseException(m_next.line, m_next.col,
        "Expected " + exp + ", got " + got);
}

std::string Lexer::tokenTypeName(TokenType t) {
    switch (t) {
        case TokenType::Null:      return "'null'";
        case TokenType::True:      return "'true'";
        case TokenType::False:     return "'false'";
        case TokenType::Number:    return "number";
        case TokenType::String:    return "string";
        case TokenType::Lbrace:    return "'{'";
        case TokenType::Rbrace:    return "'}'";
        case TokenType::Lbracket:  return "'['";
        case TokenType::Rbracket:  return "']'";
        case TokenType::Colon:     return "':'";
        case TokenType::Comma:     return "','";
        case TokenType::Eof:       return "end of file";
        default:                   return "unknown token";
    }
}

// ============================================================
//  Private helpers
// ============================================================
void Lexer::skipWhitespace() {
    while (true) {
        char c = peekChar();
        if (c == ' ' || c == '\t' || c == '\n' || c == '\r')
            advanceChar();
        else
            break;
    }
}

Token Lexer::advance() {
    skipWhitespace();
    size_t ln = m_source.line(), cl = m_source.col();
    char c = peekChar();

    switch (c) {
        case '\0': return Token(TokenType::Eof, ln, cl);
        case '{': advanceChar(); return Token(TokenType::Lbrace, ln, cl);
        case '}': advanceChar(); return Token(TokenType::Rbrace, ln, cl);
        case '[': advanceChar(); return Token(TokenType::Lbracket, ln, cl);
        case ']': advanceChar(); return Token(TokenType::Rbracket, ln, cl);
        case ':': advanceChar(); return Token(TokenType::Colon, ln, cl);
        case ',': advanceChar(); return Token(TokenType::Comma, ln, cl);
        case '"': return readString(ln, cl);
        case 't': return readKeyword("true",  TokenType::True,  ln, cl);
        case 'f': return readKeyword("false", TokenType::False, ln, cl);
        case 'n': return readKeyword("null",  TokenType::Null,  ln, cl);
        default:
            if (c == '-' || (c >= '0' && c <= '9'))
                return readNumber(ln, cl);
            throw JsonParseException(ln, cl,
                std::string("Unexpected character '") + c + "'");
    }
}

Token Lexer::readString(size_t ln, size_t cl) {
    advanceChar(); // skip opening quote
    std::string value;
    value.reserve(64);

    while (true) {
        char c = advanceChar();
        if (c == '"') {
            Token t(TokenType::String, ln, cl);
            t.stringVal = value;
            return t;
        }
        if (c == '\0')
            throw JsonParseException(ln, cl, "Unterminated string");
        if (c == '\\') {
            c = advanceChar();
            switch (c) {
                case '"':  value += '"';  break;
                case '\\': value += '\\'; break;
                case '/':  value += '/';  break;
                case 'b':  value += '\b'; break;
                case 'f':  value += '\f'; break;
                case 'n':  value += '\n'; break;
                case 'r':  value += '\r'; break;
                case 't':  value += '\t'; break;
                case 'u':  value += readUnicodeEscape(ln, cl); break;
                default:
                    throw JsonParseException(ln, cl,
                        std::string("Invalid escape character '\\") + c + "'");
            }
        } else {
            value += c;
        }
    }
}

Token Lexer::readKeyword(const char* expected, TokenType type, size_t ln, size_t cl) {
    for (const char* p = expected; *p; ++p) {
        if (peekChar() != *p)
            throw JsonParseException(ln, cl,
                std::string("Unexpected character while parsing '") + expected + "'");
        advanceChar();
    }
    return Token(type, ln, cl);
}

Token Lexer::readNumber(size_t ln, size_t cl) {
    std::string numStr;
    bool isFloat = false;

    if (peekChar() == '-') numStr += advanceChar();

    char c = peekChar();
    if (c == '0') {
        numStr += advanceChar();
    } else if (c >= '1' && c <= '9') {
        while (peekChar() >= '0' && peekChar() <= '9')
            numStr += advanceChar();
    } else {
        throw JsonParseException(ln, cl, "Unexpected character in number");
    }

    if (peekChar() == '.') {
        isFloat = true;
        numStr += advanceChar();
        if (peekChar() < '0' || peekChar() > '9')
            throw JsonParseException(ln, cl, "Expected digit after decimal point");
        while (peekChar() >= '0' && peekChar() <= '9')
            numStr += advanceChar();
    }

    if (peekChar() == 'e' || peekChar() == 'E') {
        isFloat = true;
        numStr += advanceChar();
        if (peekChar() == '+' || peekChar() == '-')
            numStr += advanceChar();
        if (peekChar() < '0' || peekChar() > '9')
            throw JsonParseException(ln, cl, "Expected digit in exponent");
        while (peekChar() >= '0' && peekChar() <= '9')
            numStr += advanceChar();
    }

    Token t(TokenType::Number, ln, cl);
    t.isFromFloat = isFloat;
    if (isFloat) {
        t.numberVal = std::stod(numStr);
    } else {
        t.intVal    = std::stoll(numStr);
        t.numberVal = static_cast<double>(t.intVal);
    }
    return t;
}

std::string Lexer::readUnicodeEscape(size_t ln, size_t cl) {
    uint32_t cp = 0;
    for (int i = 0; i < 4; ++i) {
        char c = advanceChar();
        cp <<= 4;
        if      (c >= '0' && c <= '9') cp |= static_cast<uint32_t>(c - '0');
        else if (c >= 'a' && c <= 'f') cp |= static_cast<uint32_t>(c - 'a' + 10);
        else if (c >= 'A' && c <= 'F') cp |= static_cast<uint32_t>(c - 'A' + 10);
        else throw JsonParseException(ln, cl, "Invalid unicode escape");
    }

    if (cp >= 0xD800 && cp <= 0xDBFF) {
        if (advanceChar() != '\\' || advanceChar() != 'u')
            throw JsonParseException(ln, cl,
                "Expected low surrogate after high surrogate");
        uint32_t low = 0;
        for (int i = 0; i < 4; ++i) {
            char c = advanceChar();
            low <<= 4;
            if      (c >= '0' && c <= '9') low |= static_cast<uint32_t>(c - '0');
            else if (c >= 'a' && c <= 'f') low |= static_cast<uint32_t>(c - 'a' + 10);
            else if (c >= 'A' && c <= 'F') low |= static_cast<uint32_t>(c - 'A' + 10);
            else throw JsonParseException(ln, cl, "Invalid low surrogate");
        }
        if (low < 0xDC00 || low > 0xDFFF)
            throw JsonParseException(ln, cl, "Invalid low surrogate value");
        cp = 0x10000 + ((cp - 0xD800) << 10) + (low - 0xDC00);
    } else if (cp >= 0xDC00 && cp <= 0xDFFF) {
        throw JsonParseException(ln, cl, "Unexpected lone low surrogate");
    }

    return codepointToUTF8(cp);
}

std::string Lexer::codepointToUTF8(uint32_t cp) {
    std::string result;
    if (cp < 0x80) {
        result += static_cast<char>(cp);
    } else if (cp < 0x800) {
        result += static_cast<char>(0xC0 | (cp >> 6));
        result += static_cast<char>(0x80 | (cp & 0x3F));
    } else if (cp < 0x10000) {
        result += static_cast<char>(0xE0 | (cp >> 12));
        result += static_cast<char>(0x80 | ((cp >> 6) & 0x3F));
        result += static_cast<char>(0x80 | (cp & 0x3F));
    } else {
        result += static_cast<char>(0xF0 | (cp >> 18));
        result += static_cast<char>(0x80 | ((cp >> 12) & 0x3F));
        result += static_cast<char>(0x80 | ((cp >> 6) & 0x3F));
        result += static_cast<char>(0x80 | (cp & 0x3F));
    }
    return result;
}

10.5 json/JsonParser.h

cpp 复制代码
#pragma once
#include <string>
#include <fstream>
#include "JsonValue.h"
#include "JsonLexer.h"

class Parser {
public:
    static JsonValue parseString(const std::string& json);
    static JsonValue parseStream(std::istream& is);
    static JsonValue parseFile(const std::string& path);

private:
    Lexer& m_lexer;
    Parser(Lexer& lexer) : m_lexer(lexer) {}

    JsonValue parseValue();
    JsonValue parseNumber();
    JsonValue parseStringToken();
    JsonValue parseArray();
    JsonValue parseObject();
    void parseMember(JsonValue::Object& obj);
};

10.6 json/JsonParser.cpp

cpp 复制代码
#include "JsonParser.h"

JsonValue Parser::parseString(const std::string& json) {
    StringSource src(json);
    Lexer lexer(src);
    Parser parser(lexer);
    JsonValue val = parser.parseValue();
    if (lexer.peek().type != TokenType::Eof) {
        throw JsonParseException(lexer.peek().line, lexer.peek().col,
            "Unexpected trailing content after JSON value");
    }
    return val;
}

JsonValue Parser::parseStream(std::istream& is) {
    StreamSource src(is);
    Lexer lexer(src);
    Parser parser(lexer);
    JsonValue val = parser.parseValue();
    if (lexer.peek().type != TokenType::Eof) {
        throw JsonParseException(lexer.peek().line, lexer.peek().col,
            "Unexpected trailing content after JSON value");
    }
    return val;
}

JsonValue Parser::parseFile(const std::string& path) {
    std::ifstream ifs(path.c_str(), std::ios::in | std::ios::binary);
    if (!ifs.is_open()) {
        throw std::runtime_error("Cannot open file: " + path);
    }
    return parseStream(ifs);
}

// ============================================================
//  Private parsing methods
// ============================================================
JsonValue Parser::parseValue() {
    switch (m_lexer.peek().type) {
        case TokenType::Null:   m_lexer.consume(); return JsonValue(nullptr);
        case TokenType::True:   m_lexer.consume(); return JsonValue(true);
        case TokenType::False:  m_lexer.consume(); return JsonValue(false);
        case TokenType::Number: return parseNumber();
        case TokenType::String: return parseStringToken();
        case TokenType::Lbracket: return parseArray();
        case TokenType::Lbrace:   return parseObject();
        default: {
            Token t = m_lexer.peek();
            throw JsonParseException(t.line, t.col,
                "Unexpected token: " + Lexer::tokenTypeName(t.type));
        }
    }
}

JsonValue Parser::parseNumber() {
    Token t = m_lexer.consume();
    if (t.isFromFloat) return JsonValue(t.numberVal);
    return JsonValue(t.intVal);
}

JsonValue Parser::parseStringToken() {
    Token t = m_lexer.consume();
    return JsonValue(t.stringVal);
}

JsonValue Parser::parseArray() {
    m_lexer.expect(TokenType::Lbracket);
    JsonValue::Array arr;

    if (m_lexer.peek().type == TokenType::Rbracket) {
        m_lexer.consume();
        return JsonValue(std::move(arr));
    }

    arr.push_back(parseValue());
    while (m_lexer.peek().type == TokenType::Comma) {
        m_lexer.consume();
        arr.push_back(parseValue());
    }

    m_lexer.expect(TokenType::Rbracket);
    return JsonValue(std::move(arr));
}

JsonValue Parser::parseObject() {
    m_lexer.expect(TokenType::Lbrace);
    JsonValue::Object obj;

    if (m_lexer.peek().type == TokenType::Rbrace) {
        m_lexer.consume();
        return JsonValue(std::move(obj));
    }

    parseMember(obj);
    while (m_lexer.peek().type == TokenType::Comma) {
        m_lexer.consume();
        parseMember(obj);
    }

    m_lexer.expect(TokenType::Rbrace);
    return JsonValue(std::move(obj));
}

void Parser::parseMember(JsonValue::Object& obj) {
    Token keyToken = m_lexer.consume();
    if (keyToken.type != TokenType::String) {
        throw JsonParseException(keyToken.line, keyToken.col,
                                 "Expected string key in object");
    }
    m_lexer.expect(TokenType::Colon);
    obj[keyToken.stringVal] = parseValue();
}

10.7 main.cpp

cpp 复制代码
/*
 * Pure C++17 JSON Parser - Demo
 *
 * Compile:
 *   g++ -std=c++17 -O2 -Wall -Wextra main.cpp json/JsonValue.cpp json/JsonLexer.cpp json/JsonParser.cpp -o json_parser
 *   cl /EHsc /std:c++17 /O2 main.cpp json\JsonValue.cpp json\JsonLexer.cpp json\JsonParser.cpp
 */
#include "json/JsonParser.h"
#include <iostream>
#include <sstream>

int main() {
    try {
        const std::string jsonStr = R"(
{
    "name": "C++ JSON Parser",
    "version": "1.0.0",
    "compliance": "RFC 8259",
    "features": {
        "null_support": null,
        "boolean_support": true,
        "integer_range": [-9223372036854775808, 9223372036854775807],
        "float_values": [3.14, -2.5e10, 1.0e-3, 1e20],
        "string_escapes": "hello\nworld\t\"quoted\" \\backslash",
        "unicode_support": "中文 🍎"
    },
    "tags": ["json", "c++17", "parser"],
    "empty_test": {
        "empty_array": [],
        "empty_object": {}
    },
    "nested": {
        "level1": {
            "level2": {
                "value": 42,
                "message": "deep nesting works!"
            }
        }
    }
})";

        JsonValue doc = Parser::parseString(jsonStr);
        std::cout << doc.toPrettyString() << "\n\n";

        // Field access
        std::cout << "name:    " << doc["name"].asString() << "\n";
        std::cout << "version: " << doc["version"].asString() << "\n";
        std::cout << "boolean: " << (doc["features"]["boolean_support"].asBool() ? "true" : "false") << "\n";
        std::cout << "pi:      " << doc["features"]["float_values"][size_t(0)].asDouble() << "\n";

        // Nested access
        std::cout << "nested:  " << doc["nested"]["level1"]["level2"]["message"].asString() << "\n";

        // Array iteration
        std::cout << "tags:";
        for (const auto& tag : doc["tags"])
            std::cout << " " << tag.asString();
        std::cout << "\n";

        // JSON Pointer
        std::cout << "/features/float_values/0: " << doc.query("/features/float_values/0").asDouble() << "\n";

        // Error handling
        try { Parser::parseString("{ invalid }"); }
        catch (const JsonParseException& ex) {
            std::cout << "Parse error: " << ex.what() << "\n";
        }

        // Stream input
        std::istringstream ss(R"({"a":1,"b":2})");
        JsonValue sv = Parser::parseStream(ss);
        std::cout << "Stream: " << sv.toCompactString() << "\n";

        std::cout << "All demos passed!" << std::endl;

    } catch (const std::exception& ex) {
        std::cerr << "Fatal error: " << ex.what() << "\n";
        return 1;
    }
    return 0;
}

相关推荐
Mr+范1 小时前
电源诱骗芯片CH224K
单片机·学习
AskHarries1 小时前
日志系统怎么搭
后端
swipe1 小时前
09|(前端转全栈)商品为什么不能随便上下架?后端状态机思维入门
前端·后端·全栈
程序喵大人2 小时前
【C++进阶】STL容器与迭代器 - 04 list 和 forward_list 用节点换稳定位置
开发语言·c++·list
幸福在路上wellbeing2 小时前
AI 智能体开发 · Day 1 详细学习手册
人工智能·学习
xian_wwq2 小时前
【学习笔记】框架层坍缩——LangChain 们正在被重新定义-14/15
笔记·学习·langchain
大白菜和MySQL2 小时前
elk部署和kibana图形化展示
java·笔记·elasticsearch
zhangjw342 小时前
第36篇:Spring Boot进阶:Web开发+参数校验+全局异常处理
前端·spring boot·后端
倒流时光三十年2 小时前
第一阶段 02 · Mapping 与数据类型(text vs keyword 是重点)
后端·python·django