本文记录了一个纯 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 种数据类型:null、bool、int、double、string、array、object。在 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 截断,但 bool 或 string 调用 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 处理:
- 解析
\uXXXX为 16 位码点 - 检测 surrogate pair:
\uD800-\uDBFF(高代理) +\uDC00-\uDFFF(低代理) - 将码点编码为 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+20 → 1e+20.0 |
识别指数位,在指数前加 .0 |
审查的另一个重要产出是代码解耦:从单文件(1100 行)拆分为 4 个独立模块,每文件 100-300 行,降低了认知负载。
九、总结
写一个 JSON 解析器,让我意识到几个事情:
- RFC 8259 看似简单,细节不少。字符串转义、Unicode surrogate pair、数字格式的边界情况,都需要仔细处理。
- 浮点数精度是暗坑 。用
double中转整数的做法在超过 2⁵³ 时悄无声息地丢失精度。这个 bug 在单元测试中很容易被忽略。 - 设计审查的价值。单文件工作时觉得一切都好,回头看才发现 6 个 P0/P1 级别的问题。代码解耦也让模块间的接口更清晰。
- 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;
}