Jsoncpp 的介绍与使用

一、JSON 格式

JSON(JavaScript Object Notation,JavaScript 对象表示法)是一种轻量级的文本数据交换格式。它易于人阅读和编写,也易于机器解析和生成。尽管 JSON 源自 JavaScript,但它是一种与编程语言无关的格式,目前绝大多数现代编程语言都支持它。

1.1 基本的语法规则

  • 数据以键值对形式存在"key": value

  • 键(Key):必须是双引号包裹的字符串。

  • 值(Value):可以是以下类型:

    • 字符串"hello"
    • 数字1003.14
    • 布尔值truefalse
    • 数组["apple" , "banana"]
    • 对象{"name": "Alice"}
    • 空值null
  • 数据之间用逗号分隔

  • 不允许在最后一个成员后添加多余的逗号

1.2 示例

JSON 通常由两种结构嵌套组成:

  • 对象(Object):花括号 {} 包裹的无序键值对集合。

  • 数组(Array):方括号 [] 包裹的有序值列表。

{
    "name": "张三",
    "age": 30,
    "isStudent": false,
    "hobbies": ["阅读", "编程", "游泳"],
    "address": {
        "city": "北京",
        "zipcode": "100000"
    },
    "project": null
}

1.3 场景

  • 数据传输:前后端交互的主流数据格式。

  • 配置文件:用于应用程序的配置信息。

  • 数据存储:作为⼀种数据格式,⽤于⽂件和数据库存储。

二、Jsoncpp 使用

2.1 Jsoncpp 介绍

JsonCpp 是一个在 C++ 中处理 JSON 数据的开源库,它提供了一套简单易用的 API,让你可以方便地在 C++ 对象和 JSON 字符串之间进行转换(即序列化与反序列化)。它轻量、跨平台,是 C++ 项目中最常用的 JSON 库之一。

  • 序列化(Serialization):将内存中的对象或数据结转换为一种可存储或可传输的格式的过程。

  • 反序列化(Deserialization):序列化的逆过程,将存储或传输格式的数据还原为内存中的对象或数据结构。

Jsoncpp 的序列化就是将内存中的对象转换成 JSON 格式的文本字符串,这个字符串是明文(可读的)。

2.2 Jsoncpp 安装

sudo apt-get install libjsoncpp-dev

2.3 头文件包含

#include <jsoncpp/json/json.h>

2.4 程序编译时的链接

g++ main.cc -o main -ljsoncpp

2.5 Json::Value 数据转储对象

Json::Value 是 jsoncpp 库中最核心的类,它代表一个 JSON 数据实体,可以容纳 JSON 规范定义的所有数据类型(null、布尔值、数值、字符串、数组、对象),并提供了统一的访问接口。

  • 序列化:通过 Json::StreamWriterJson::Value 转换为 JSON 字符串。

  • 反序列化:通过 Json::CharReader 将 JSON 字符串解析为 Json::Value

namespace Json {
    class JSON_API Value {
        public:
            /**
            * @brief 交换两个 Value 对象的内容(高效,不进行深拷贝)
            * @param other 要交换的另一个 Value 对象
            */
            void swap(Value& other);

            /**
            * @brief 将另一个 Value 对象的内容复制到当前对象(深拷贝)
            * @param other 源 Value 对象
            */
            void copy(const Value& other);

            /**
            * @brief 将 Value 对象中的元素以什么类型返回
            * @note 仅当 Value 为对应类型时,否则可能返回空指针或触发断言
            * @return 指向字符串内部缓冲区的指针
            */
            const char* asCString() const;

            /**
            * @brief 将 Value 以 std::string 形式返回
            * @return 字符串副本(若 Value 不是字符串类型,则尝试转换)
            */
            String asString() const;

            /**
            * @brief 将 Value 转换为有符号 32 位整数返回
            * @note 若 Value 为数值型,会进行截断或舍入;若非数值型,行为未定义(通常返回 0)
            * @return 转换后的整数值
            */
            Int asInt() const;

            /**
            * @brief 将 Value 转换为无符号 32 位整数返回
            * @return 转换后的整数值
            */
            UInt asUInt() const;

            /**
            * @brief 将 Value 转换为有符号 64 位整数返回
            * @return 转换后的整数值
            */
            Int64 asInt64() const;

            /**
            * @brief 将 Value 转换为无符号 64 位整数返回
            * @return 转换后的整数值
            */
            UInt64 asUInt64() const;

            /**
            * @brief 将 Value 转换为单精度浮点数返回
            * @return 转换后的浮点值
            */
            float asFloat() const;

            /**
            * @brief 将 Value 转换为双精度浮点数返回
            * @return 转换后的浮点值
            */
            double asDouble() const;

            /**
            * @brief 将 Value 转换为布尔值返回
            * @return 转换后的布尔值(对于非布尔型,根据特定规则转换,如 0→false, 1→true 等)
            */
            bool asBool() const;

            /**
            * @brief 判断当前 Value 是否为 null(即 JSON 中的 null)
            * @return true 表示为 null,否则为 false
            */
            bool isNull() const;

            /**
            * @brief 判断当前 Value 是否为布尔类型
            * @return true 表示为布尔类型,否则为 false
            */
            bool isBool() const;

            /**
            * @brief 判断当前 Value 是否为 32 位有符号整数类型(或可无损转换为该类型)
            * @return true 表示可以安全地作为 Int 使用
            */
            bool isInt() const;

            /**
            * @brief 判断当前 Value 是否为 64 位有符号整数类型(或可无损转换)
            * @return true 表示可以安全地作为 Int64 使用
            */
            bool isInt64() const;

            /**
            * @brief 判断当前 Value 是否为 32 位无符号整数类型(或可无损转换)
            * @return true 表示可以安全地作为 UInt 使用
            */
            bool isUInt() const;

            /**
            * @brief 判断当前 Value 是否为 64 位无符号整数类型(或可无损转换)
            * @return true 表示可以安全地作为 UInt64 使用
            */
            bool isUInt64() const;

            /**
            * @brief 判断当前 Value 是否为整数类型(包括有符号和无符号,不包括浮点)
            * @return true 表示为整数类型
            */
            bool isIntegral() const;

            /**
            * @brief 判断当前 Value 是否为双精度浮点数类型(或可无损转换为 double)
            * @return true 表示为浮点类型
            */
            bool isDouble() const;

            /**
            * @brief 判断当前 Value 是否为数值类型(整数或浮点数)
            * @return true 表示为数值类型
            */
            bool isNumeric() const;

            /**
            * @brief 判断当前 Value 是否为字符串类型
            * @return true 表示为字符串类型
            */
            bool isString() const;

            /**
            * @brief 判断当前 Value 是否为数组类型(JSON 数组)
            * @return true 表示为数组
            */
            bool isArray() const;

            /**
            * @brief 判断当前 Value 是否为对象类型(JSON 对象)
            * @return true 表示为对象
            */
            bool isObject() const;

            // ----- 数组/对象通用 API -----

            /**
            * @brief 返回数组或对象中元素的个数
            * @note 若 Value 不是数组也不是对象,则返回 0
            * @return 元素个数(数组长度或对象成员数量)
            */
            ArrayIndex size() const;

            /**
            * @brief 判断 Value 为空,也能判断 Value 对象中的元素(数组或对象)是否为空(size() == 0)
            * @return true 表示为空
            */
            bool empty() const;

            /**
            * @brief 清空 Value 的内容,将其重置为 null
            */
            void clear();

            /**
            * @brief 调整数组的大小
            * @param newSize 新的数组长度
            * @note 如果当前 Value 不是数组,则先转换为空数组;若新长度大于原长度,新增元素为 null
            */
            void resize(ArrayIndex newSize);

            // ----- 数组专用 API -----

            /**
            * @brief 通过下标访问数组元素(返回引用,可修改)
            * @param index 数组下标(从 0 开始)
            * @return 对应元素的引用,若下标超出当前数组范围且 Value 是数组,则会自动扩展
            * @note 若当前 Value 不是数组,则先转换为数组
            */
            Value& operator[](ArrayIndex index);

            /**
            * @brief 向数组末尾追加一个元素
            * @param value 要追加的值
            * @return 新添加元素的引用
            * @note 若当前 Value 不是数组,则先转换为数组
            */
            Value& append(const Value& value);

            // ----- 对象专用 API -----

            /**
            * @brief 通过 C 字符串键访问对象成员(返回引用,可修改)
            * @param key 成员名(C 字符串)
            * @return 对应成员的引用,若成员不存在则自动创建为 null
            * @note 若当前 Value 不是对象,则先转换为对象
            */
            Value& operator[](const char* key);

            /**
            * @brief 通过 std::string 键访问对象成员(返回引用,可修改)
            * @param key 成员名
            * @return 对应成员的引用,若成员不存在则自动创建为 null
            * @note 若当前 Value 不是对象,则先转换为对象
            */
            Value& operator[](const String& key);

            /**
            * @brief 移除对象中指定键的成员
            * @param key 要移除的成员名
            * @note 若键不存在,则无效果
            */
            void removeMember(const String& key);

            /**
            * @brief 判断对象中是否存在指定的键
            * @param key 要查询的成员名
            * @return true 表示存在该成员
            */
            bool isMember(const String& key) const;

            /**
            * @brief 将 Value 格式化为带缩进的 JSON 字符串(便于阅读)
            * @return 格式化后的字符串
            * @note 该函数主要用于调试或输出,性能较低;生产环境建议使用 StreamWriter
            */
            String toStyledString() const;
        };
}

2.6 Json::Value 示例

#include <jsoncpp/json/json.h>
#include <iostream>

int main() {

    Json::Value root;
    // 添加元素
    root["name"] = "张三";
    root["age"] = 25;
    root["address"]["city"] = "北京";   // 自动创建 address 对象
    root["score"].append(10);      // 自动创建 score 数组
    root["score"].append(20.5);
    std::cout << root.toStyledString() << std::endl;

    // 访问元素
    std::cout << root["name"].asString() << std::endl;
    std::cout << root["age"].asInt() << std::endl;
    std::cout << root["address"]["city"].asString() << std::endl;
    // 访问数组元素
    size_t n = root["score"].size();
    for(int i = 0; i < n; i++)
        std::cout << root["score"][i] << std::endl;

    return 0;
}

// 输出结果

{
        "address" : 
        {
                "city" : "\u5317\u4eac"
        },
        "age" : 25,
        "name" : "\u5f20\u4e09",
        "score" : 
        [
                10,
                20.5
        ]
}

张三
25
北京
10
20.5

2.7 序列化

StreamWriter 是 jsoncpp 中负责序列化(将 Json::Value 写入流)的抽象基类。它定义了将 JSON 数据写入输出流的统一接口,具体的格式化行为由其子类实现。通常不直接实例化,而是通过 StreamWriterBuilder 工厂创建。

StreamWriterBuilderjsoncpp 库中用于序列化(将 Json::Value 转换为字符串)的工厂类,它负责创建 StreamWriter 对象,并允许你精细地控制输出的 JSON 格式。

原型:

namespace Json {
    class JSON_API StreamWriter {
    public:
        virtual ~StreamWriter();

        // 核心方法:将 Value 写入输出流
        virtual int write(Value const& root, std::ostream* sout) = 0;
    };
}

namespace Json {
    class JSON_API StreamWriterBuilder : public StreamWriter::Factory {
    public:
        // 构造函数
        StreamWriterBuilder();
        virtual ~StreamWriterBuilder();

        // 核心工厂方法
        virtual StreamWriter* newStreamWriter() const;

        // 配置访问接口
        Value& operator[](const String& key);
        const Value& operator[](const String& key) const;

        // 成员变量
        Value settings_;
    };
}

⚠️ write返回值:成功返回0,失败返回非0。

常用选项配置

Json::StreamWriterBuilder builder;
builder["indentation"] = ""; // 表示紧凑输出
builder["emitUTF8"] = true; // true: 直接输出中文等非 ASCII 字符(如 "张三");false: 将非 ASCII 字符转义为 \uXXXX 格式(如 "\u5F20\u4E09")
builder["dropNullPlaceholders"] = true; // 是否丢弃 null 值(默认 false)
builder["precision"] = 2; // 表示输出浮点数时保留多少位有效数字或小数位数
builder["commentStyle"] = "None"; // 默认None不输出注释,All输出所有注释

2.8 反序列化

CharReader 是反序列化的抽象基类,定义了将 JSON 字符串解析为 Json::Value 的接口。通常不直接实例化,而是通过 CharReaderBuilder 工厂创建。

CharReaderBuilder 是反序列化的工厂类,负责创建 CharReader 对象,并允许配置解析行为。

原型

namespace Json {
    class JSON_API CharReader {
    public:
        virtual ~CharReader();
        
        // 核心方法:解析 JSON 字符串
        virtual bool parse(char const* begin, char const* end,
                          Value* root, String* errs) = 0;
    };
}

namespace Json {
    class JSON_API CharReaderBuilder : public CharReader::Factory {
    public:
        CharReaderBuilder();
        virtual ~CharReaderBuilder();
        
        // 工厂方法:创建 CharReader 对象
        virtual CharReader* newCharReader() const;
        
        // 配置访问接口
        Value& operator[](const String& key);
        const Value& operator[](const String& key) const;
        
        // 配置存储
        Value settings_;
    };
}

⚠️ parse返回值:成功返回 true,失败返回 false。

2.9 基本使用

#include <jsoncpp/json/json.h>
#include <iostream>
#include <sstream>

int main() {

    Json::Value root;
    // 添加元素
    root["name"] = "张三";
    root["age"] = 25;
    root["address"]["city"] = "北京";   // 自动创建 address 对象
    root["score"].append(10);      // 自动创建 score 数组
    root["score"].append(20.5);

    // 序列化
    Json::StreamWriterBuilder writeBuilder;
    std::unique_ptr<Json::StreamWriter> writer(writeBuilder.newStreamWriter());
    std::stringstream ss;
    writer->write(root, &ss);
    std::cout << "序列化:" << std::endl;
    std::cout << ss.str() << std::endl;
    // 反序列化
    std::string json_str = ss.str();
    Json::CharReaderBuilder readBuilder;
    std::unique_ptr<Json::CharReader> reader(readBuilder.newCharReader());
    std::string err;
    Json::Value res;
    reader->parse(json_str.c_str() , json_str.c_str() + json_str.size() , &res , &err);
    std::cout << "反序列化:" << std::endl;
    std::cout << res["name"].asString() << std::endl;
    std::cout << res["age"].asInt() << std::endl;
    if(!res["address"].isNull() && res["address"].isObject())
        std::cout << res["address"]["city"].asString() << std::endl;
    if(!res["score"].isNull() && res["score"].isArray()){
        size_t n = res["score"].size();
        for(int i = 0; i < n; i++)
            std::cout << res["score"][i] << std::endl;
    }


    return 0;
}

// 输出结果
序列化:
{
        "address" : 
        {
                "city" : "\u5317\u4eac"
        },
        "age" : 25,
        "name" : "\u5f20\u4e09",
        "score" : 
        [
                10,
                20.5
        ]
}
反序列化:
张三
25
北京
10
20.5

三、Jsoncpp 二次封装

util.h

#pragma once

#include <jsoncpp/json/json.h>
#include <iostream>
#include <sstream>
#include <memory>
#include <optional>

namespace util {
    // JSON 而不是 Json 防止和 jsoncpp 中的重名
    struct JSON {
        // 注意,函数实现时,无需加 static 和 缺省参数
        static std::optional<std::string> serialize(const Json::Value& value , bool style = false);
        static std::optional<Json::Value> unserialize(const std::string& input);
    };
}
  • 类成员函数定义时需要 static,实现时无需 static 。

  • 类成员函数定义时的缺省参数,实现时不需要缺省参数。

  • 定义时需要类域声明。

util.cc

#include "util.h"

namespace util {
    
    // 定义不需要 static 关键字
    std::optional<std::string> JSON::serialize(const Json::Value& value , bool style) {
        Json::StreamWriterBuilder writeBuilder;
        if(style) {
            writeBuilder["emitUTF8"] = true;
            writeBuilder["indentation"] = " ";
        } else {
            writeBuilder["emitUTF8"] = false;
            writeBuilder["indentation"] = "";
        }
        std::unique_ptr<Json::StreamWriter> writer(writeBuilder.newStreamWriter());
        std::stringstream ss;
        if(writer->write(value, &ss)) {
            // 序列化失败
            std::cout << "序列化失败!" << std::endl;
            return std::nullopt;
        }
        return ss.str();
    }   

    std::optional<Json::Value> JSON::unserialize(const std::string& input) {
        Json::CharReaderBuilder readBuilder;
        std::unique_ptr<Json::CharReader> reader(readBuilder.newCharReader());
        std::string err;
        Json::Value res;
        if(!reader->parse(input.c_str() , input.c_str() + input.size() , &res , &err)) {
            // 反序列化失败
            std::cout << input << " 序列化失败: " << err << std::endl;
            return std::nullopt;
        }
        return res;
    }

}

test.cc

#include "util.h"

int main() {

    // 序列化测试
    Json::Value root;
    root["name"] = "张三";
    root["age"] = 25;
    root["address"]["city"] = "北京";   
    root["score"].append(10);      
    root["score"].append(20.5);
    std::optional<std::string> json_str = util::JSON::serialize(root);
    if(json_str) {
        std::cout << "序列化结果\n" << json_str.value() << std::endl;
    }

    // 反序列化测试
    std::string str = R"({
        "address":{
            "city":"Anytown",
            "state":"CA",
            "street":"123 Main St"
        },
        "age":30,
        "is_student":false,
        "name":"John Doe"
    })";
    std::optional<Json::Value> val =  util::JSON::unserialize(str);
    if(val) {
        std::cout << "反序列化结果\n";
        std::cout << val.value()["address"]["city"].asString() << std::endl;
        std::cout << val.value()["address"]["state"].asString() << std::endl;
        std::cout << val.value()["address"]["street"].asString() << std::endl;
        std::cout << val.value()["age"].asInt() << std::endl;
        std::cout << val.value()["name"].asString() << std::endl;
        std::cout << val.value()["is_student"].asBool() << std::endl;
    }

    return 0;
}

输出结果:

序列化结果
{"address":{"city":"\u5317\u4eac"},"age":25,"name":"\u5f20\u4e09","score":[10,20.5]}
反序列化结果
Anytown
CA
123 Main St
30
John Doe
0

四、补充概念

4.1 R"()"

R"()" 原始字符串字面量(Raw String Literal),是 C++11 引入的原始字符串字面量语法,用于表示字符串而不需要转义特殊字符。

使用场景:

  • JSON 字符串

  • 正则表达式

  • 避免转移字符

  • 多行字符串

避免转移字符

// 传统方式:需要转义引号和反斜杠
std::string json1 = "{\"name\":\"张三\",\"age\":25}";

// 原始字符串:直接写,无需转义
std::string json2 = R"({"name":"张三","age":25})";

// 传统方式:需要转义反斜杠
std::string path1 = "C:\\Users\\name\\file.txt";

// 原始字符串:直接写
std::string path2 = R"(C:\Users\name\file.txt)";

多行字符串无需续行符

std::string str = R"({
        "address":{
            "city":"Anytown",
            "state":"CA",
            "street":"123 Main St"
        },
        "age":30,
        "is_student":false,
        "name":"John Doe"
    })";

4.2 std::optional

std::optional 是 C++17 引入的一个标准库特性,定义在 <optional> 头文件中。

它本质上是一个容器适配器,用来表示一个对象可能存在,也可能不存在的状态。你可以把它理解为一个安全的包装器,它要么包含一个有效值,要么处于空 std::nullopt 状态。

核心用途

std::optional 出现之前,当函数需要表达可能无返回值时,通常有以下几种处理方式,但各有缺陷:

  • 返回特殊值(如 -1nullptr):依赖于调用方记住并检查该特殊值,且特殊值可能本身就是合法值。

  • 通过输出参数 + bool 返回值:代码不够直观等问题。

std::optional 优雅地解决了这些问题,将值的存在性作为类型的一部分显式表达出来。

  • 构造:可以直接用 T 类型的值构造,或者用 std::nullopt 构造为空。

  • has_value()operator bool 检查是否有值。

  • value()operator* / operator-> 访问内部值(前提确保 optional 非空)。

  • value_or(默认值) 没有返回值,则使用默认值。

  • reset() 清空 optional。

示例:

#include <iostream>
#include <optional>
#include <string>

// 函数:根据 ID 查找名字,可能找不到
std::optional<std::string> findName(int id) {
    if (id == 1) {
        return "Alice";   // 包含值
    }
    if (id == 2) {
        return "Bob";
    }
    return std::nullopt;  // 明确表示无值
}

int main() {
    // 1. 检查是否有值
    std::optional<std::string> result = findName(1);
    if (result.has_value()) {
        std::cout << "Found: " << *result << "\n"; // 使用 * 或 .value() 访问
    }
    
    // 2. 使用 value_or 提供默认值
    std::cout << findName(99).value_or("Unknown") << "\n"; // 输出 Unknown
    
    // 3. 直接用于条件判断 (支持 bool 转换)
    std::optional<std::string> name = findName(2);
    if (name) {
        std::cout << "Found: " << *name << "\n";
    }
    
    return 0;
}
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐