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

0 阅读5分钟

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

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

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

框架与执行流程

整体框架:

jsoncpp-arch.png

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

jsoncpp-core-flow.png

核心结构

源码地图:

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 实现多类型存储
  • 统一的数据模型,隔离解析与序列化
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 数据,将原对象置;完全避免了底层字符串数据的物理拷贝。
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%
class Value {
    struct CommentInfo {
        char* comment_;
    };
    std::unique_ptr<CommentInfo[]> comments_;  // [Before, AfterSameLine, After]
};

解析时收集:

if (collectComments_ && !commentsBefore_.empty()) {
    currentValue().setComment(commentsBefore_, commentBefore);
}

序列化时输出:

if (value.hasComment(commentBefore)) {
    *sout_ << value.getComment(commentBefore) << "\n";
}

错误处理策略

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

#if JSON_USE_EXCEPTION
    throw RuntimeError(msg);
#else
    std::cerr << msg << std::endl;
    abort();
#endif

结构化错误报告:

struct StructuredError {
    ptrdiff_t offset_start;  // Error location in document
    ptrdiff_t offset_limit;
    String message;
};

安全内存分配器

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

  • 防止敏感数据残留(密码、密钥)
  • 性能下降约 5-10%(内存清零开销)
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/objectValueValue 对象
类型检查bool isString() consttrue/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()迭代器

使用样例:

#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::stringgetAllowedOrigins() 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;
}

性能相关参数

// 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