C++之轻量级JSON序列库jsoncpp

更多 C++ 文章见《修远之路(C++集萃)》专栏

JsonCpp 是一个基于 Tagged Union 实现的轻量级 C++ JSON 序列化/反序列化库,专注于易用性与可维护性。提供直观的 DOM 风格 API,支持注释保留,零外部依赖,MIT 许可证

能力 适用场景 不适用场景
DOM 风格 API 配置文件读写、RPC 数据交换、中小型 JSON 文档 流式处理超大 JSON(GB 级)
注释保留 用户配置文件、需要人工审阅的 JSON 机器间通信、性能敏感场景
类型安全 编译期类型检查、防御性编程 动态类型语言绑定
零拷贝优化 静态字符串字面量作为 key 频繁修改的动态字符串
内存安全 敏感数据处理(密码、密钥) 普通应用(性能优先)

框架与执行流程

整体框架:

核心执行时序(解析流程):

核心结构

源码地图:

css 复制代码
jsoncpp-1.9.6/
├── include/json/
│   ├── value.h          [核心] Value 类定义,Tagged Union 接口
│   ├── reader.h         [核心] CharReader/Builder,解析器接口
│   ├── writer.h         [核心] StreamWriter/Builder,序列化器接口
│   ├── allocator.h      [关键] SecureAllocator,安全内存管理
│   └── config.h         [基础] 类型定义,平台适配
└── src/lib_json/
    ├── json_value.cpp   [核心] Value 实现,CZString 实现
    ├── json_reader.cpp  [核心] 解析器实现,递归下降解析
    └── json_writer.cpp  [核心] 序列化器实现,格式化输出

Tagged Union 存储模型

Value:JSON 值的内存表示与操作

  • 使用 Tagged Union 实现多类型存储
  • 统一的数据模型,隔离解析与序列化
arduino 复制代码
class Value {
private:
    union ValueHolder {
        Int int_;
        UInt uint_;
        double real_;
        char* string_;
        bool bool_;
        ObjectValues* map_;  // for array/object
    } value_;
    
    ValueType type_;  // Tag field
    // ... comments, offsets
};

优势:

  • 内存紧凑:单个 Value 对象固定大小(约 24-32 字节)
  • 类型安全:运行时类型检查,避免未定义行为
  • 零开销:无虚函数表,无动态分发

代价:

  • Object/Array 需要堆分配(new ObjectValues
  • 类型转换需运行时检查(switch-case)

字符串存储优化:CZString

CZString优化的字符串键存储:

  • 实现三种字符串存储策略
  • 减少字符串拷贝,支持零拷贝优化
  • 在分配字符串内存时,在实际字符串内容的前面多分配 4 个字节,存储字符串的长度;可直接获取长度,避免遍历计算
  • 指针所有权的转移:定义了移动构造函数和移动赋值运算符;对象在 std::map 中发生重排或作为右值传递时,可直接"窃取"原对象的 cstr_ 指针和 union 数据,将原对象置;完全避免了底层字符串数据的物理拷贝。
arduino 复制代码
class CZString {
    enum DuplicationPolicy {
        noDuplication = 0,      // Static string (zero-copy)
        duplicate,              // Heap-allocated copy
        duplicateOnCopy         // Copy-on-write (deferred)
    };
    
    char const* cstr_;
    union {
        ArrayIndex index_;      // For array indexing
        struct {
            unsigned policy_ : 2;
            unsigned length_ : 30;  // Max 1GB string
        } storage_;
    };
};

注释保留机制

JsonCpp 在 Value 中存储三种位置的注释:

  • 配置文件可读性高,支持人工编辑
  • 内存开销增加,解析性能下降约 10-15%
c 复制代码
class Value {
    struct CommentInfo {
        char* comment_;
    };
    std::unique_ptr<CommentInfo[]> comments_;  // [Before, AfterSameLine, After]
};

解析时收集:

scss 复制代码
if (collectComments_ && !commentsBefore_.empty()) {
    currentValue().setComment(commentsBefore_, commentBefore);
}

序列化时输出:

scss 复制代码
if (value.hasComment(commentBefore)) {
    *sout_ << value.getComment(commentBefore) << "\n";
}

错误处理策略

JsonCpp 提供可配置的错误处理:

c 复制代码
#if JSON_USE_EXCEPTION
    throw RuntimeError(msg);
#else
    std::cerr << msg << std::endl;
    abort();
#endif

结构化错误报告:

arduino 复制代码
struct StructuredError {
    ptrdiff_t offset_start;  // Error location in document
    ptrdiff_t offset_limit;
    String message;
};

安全内存分配器

可选的 SecureAllocator 实现敏感数据保护:

  • 防止敏感数据残留(密码、密钥)
  • 性能下降约 5-10%(内存清零开销)
arduino 复制代码
template<typename T>
class SecureAllocator {
    void deallocate(pointer p, size_type n) {
        memset_s(p, n * sizeof(T), 0, n * sizeof(T));  // Secure wipe
        ::operator delete(p);
    }
};

常用 API

常用 API 表格:

API 类别 核心方法 参数说明 返回值
Value 构造 Value(ValueType type) nullValue/arrayValue/objectValue Value 对象
类型检查 bool isString() const true/false
类型转换 String asString() const 字符串值
数组操作 Value& append(const Value&) 要追加的值 引用
对象访问 Value& operator[](const char* key) 键名 值引用
安全访问 Value get(key, default) const 键名、默认值 值副本
解析 bool parse(text, root, comments) JSON 文本、输出根、是否保留注释 成功/失败
序列化 String write(root) Value 对象 JSON 字符串
迭代器 iterator begin()/end() 迭代器

使用样例:

c 复制代码
#include <json/json.h>
#include <fstream>
#include <iostream>

class ConfigManager {
public:
    bool loadConfig(const std::string& filepath) {
        std::ifstream configFile(filepath);
        if (!configFile.is_open()) {
            std::cerr << "[ERROR] Failed to open config file: " << filepath << std::endl;
            return false;
        }

        Json::CharReaderBuilder builder;
        builder["collectComments"] = true;  // Preserve comments
        builder["allowComments"] = true;    // Allow // and /* */ comments
        
        Json::Value root;
        std::string errors;
        
        if (!Json::parseFromStream(builder, configFile, &root, &errors)) {
            std::cerr << "[ERROR] JSON parse error: " << errors << std::endl;
            return false;
        }

        if (!validateConfig(root)) {
            std::cerr << "[ERROR] Invalid configuration structure" << std::endl;
            return false;
        }

        config_ = std::move(root);
        return true;
    }

    bool saveConfig(const std::string& filepath) {
        std::ofstream configFile(filepath);
        if (!configFile.is_open()) {
            std::cerr << "[ERROR] Failed to create config file: " << filepath << std::endl;
            return false;
        }

        Json::StreamWriterBuilder builder;
        builder["commentStyle"] = "All";        // Preserve comments
        builder["indentation"] = "    ";        // 4-space indentation
        builder["enableYAMLCompatibility"] = true;  // YAML-friendly
        
        std::unique_ptr<Json::StreamWriter> writer(builder.newStreamWriter());
        if (writer->write(config_, &configFile) != 0) {
            std::cerr << "[ERROR] Failed to write config" << std::endl;
            return false;
        }
        
        return true;
    }

    int getServerPort() const {
        return config_.get("server", Json::Value())
                     .get("port", 8080)  // Default port
                     .asInt();
    }

    std::string getDatabaseHost() const {
        const Json::Value& db = config_.get("database", Json::Value());
        return db.get("host", "localhost").asString();
    }

    std::vector<std::string> getAllowedOrigins() const {
        std::vector<std::string> origins;
        const Json::Value& cors = config_.get("cors", Json::Value());
        const Json::Value& allowList = cors.get("allowed_origins", Json::arrayValue);
        
        for (const auto& origin : allowList) {
            origins.push_back(origin.asString());
        }
        
        return origins;
    }

    void setServerPort(int port) {
        config_["server"]["port"] = port;
    }

private:
    bool validateConfig(const Json::Value& root) {
        if (!root.isObject()) {
            std::cerr << "[VALIDATION] Root must be an object" << std::endl;
            return false;
        }

        if (root.isMember("server")) {
            const Json::Value& server = root["server"];
            if (!server.isObject()) {
                std::cerr << "[VALIDATION] 'server' must be an object" << std::endl;
                return false;
            }
            if (server.isMember("port")) {
                if (!server["port"].isInt()) {
                    std::cerr << "[VALIDATION] 'server.port' must be an integer" << std::endl;
                    return false;
                }
                int port = server["port"].asInt();
                if (port < 1 || port > 65535) {
                    std::cerr << "[VALIDATION] 'server.port' out of range [1, 65535]" << std::endl;
                    return false;
                }
            }
        }

        return true;
    }

    Json::Value config_;
};

int main() {
    ConfigManager configMgr;
    
    // Create sample config
    Json::Value sampleConfig;
    sampleConfig["server"]["port"] = 9000;
    sampleConfig["server"]["host"] = "0.0.0.0";
    sampleConfig["database"]["host"] = "db.example.com";
    sampleConfig["database"]["port"] = 5432;
    sampleConfig["database"]["name"] = "production";
    sampleConfig["cors"]["allowed_origins"].append("https://example.com");
    sampleConfig["cors"]["allowed_origins"].append("https://api.example.com");
    
    // Add comment
    sampleConfig["server"].setComment("// Server configuration", Json::commentBefore);
    
    configMgr.config_ = sampleConfig;
    
    if (configMgr.saveConfig("config.json")) {
        std::cout << "[INFO] Config saved successfully" << std::endl;
    }
    
    if (configMgr.loadConfig("config.json")) {
        std::cout << "[INFO] Server port: " << configMgr.getServerPort() << std::endl;
        std::cout << "[INFO] Database host: " << configMgr.getDatabaseHost() << std::endl;
        
        auto origins = configMgr.getAllowedOrigins();
        std::cout << "[INFO] Allowed origins (" << origins.size() << "):" << std::endl;
        for (const auto& origin : origins) {
            std::cout << "  - " << origin << std::endl;
        }
    }
    
    return 0;
}

性能相关参数

c 复制代码
// 1. Disable comments for performance-critical paths
Json::CharReaderBuilder builder;
builder["collectComments"] = false;  // ~15% faster parsing

// 2. Use FastWriter for minimal serialization
Json::FastWriter fastWriter;
std::string compact = fastWriter.write(value);  // No indentation

// 3. Reserve array capacity
Json::Value array(Json::arrayValue);
for (int i = 0; i < 10000; ++i) {
    array.append(i);  // Automatic resizing
}

// 4. Use StaticString for constant keys
static const Json::StaticString ID_KEY("id");
object[ID_KEY] = 123;  // Zero-copy key insertion
相关推荐
handler0114 分钟前
【算法】并查集(普通/扩展/带权)模板与例题
数据结构·c++·笔记·算法·c·图论·查并集
繁星蓝雨1 小时前
C++中对比pragma once和ifndef的使用区别
开发语言·c++·ifndef·头文件·pragma once
.千余1 小时前
【C++】C++手写Vector容器:从底层源码模拟实现
开发语言·c++·经验分享·笔记·学习
a诠释淡然1 小时前
C++ vs Rust:哪个更适合你的下一个项目?
开发语言·c++·rust
小小de风呀1 小时前
de风——【从零开始学C++】(十二):stack和queue的基本使用和模拟实现
开发语言·c++
汉克老师1 小时前
GESP6级C++考试语法知识(五十三、动态规划----背包问题(六、分组背包)
c++·动态规划·背包问题·gesp6级·gesp六级·分组背
雪度娃娃2 小时前
转向现代C++——保证const成员函数的线程安全性
开发语言·c++
坚果派·白晓明2 小时前
[鸿蒙PC三方库移植适配] 使用 AtomCode + Skills 自动完成Protobuf鸿蒙化适配
c语言·c++·华为·harmonyos
原来是猿2 小时前
深入理解 C++ unordered_map 与 unordered_set
开发语言·c++
满天星83035772 小时前
【Qt】信号和槽 (一)(概述和基本使用)
开发语言·c++·qt