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
相关推荐
咩咦1 小时前
C++学习笔记09:内联函数 inline
c++·学习笔记·inline·内联函数·宏函数
彭于晏Yan1 小时前
HttpServletRequest 如何读取JSON请求体
spring boot·后端·json
计算机安禾1 小时前
【c++面向对象编程】第19篇:多继承与菱形继承(二):虚拟继承的内存模型与复杂性
开发语言·c++
思麟呀1 小时前
在C++基础上理解CSharp-1
开发语言·c++·c#
学习,学习,在学习2 小时前
Q工控仪器程序框架设计详解(工控)
c++·qt·架构·qt5
j7~2 小时前
【Linux系统】基础IO(文件描述)(1)
linux·服务器·c++·文件·基础io
计算机安禾2 小时前
【c++面向对象编程】第20篇:override与final关键字:现代C++对继承的控制
开发语言·c++
郝学胜-神的一滴2 小时前
Qt 高级开发 004: 三大窗口类深度解析
开发语言·c++·qt·程序人生·系统架构
王老师青少年编程2 小时前
csp信奥赛C++高频考点专项训练之字符串 --【字符串综合】:[NOIP 2004 普及组] FBI 树
c++·字符串·csp·高频考点·信奥赛·字符串综合·fbi树