JsonCpp:C++ JSON处理利器

目录

一、概述

二、核心特性

[三、安装 JsonCpp](#三、安装 JsonCpp)

[1、Ubuntu / Debian](#1、Ubuntu / Debian)

[2、CentOS / RHEL / Fedora](#2、CentOS / RHEL / Fedora)

3、编译时显示链接库

[4、查看是否已安装 JsonCpp](#4、查看是否已安装 JsonCpp)

5、主要类介绍

[四、JSON 数据格式转化](#四、JSON 数据格式转化)

[1、JSON 的数据格式](#1、JSON 的数据格式)

2、序列化与反序列化

总结

五、序列化

[1、使用 Json::Value::toStyledString 方法](#1、使用 Json::Value::toStyledString 方法)

[2、使用 Json::StreamWriterBuilder (现代推荐方式)](#2、使用 Json::StreamWriterBuilder (现代推荐方式))

[3、使用 Json::FastWriter (已弃用,但需了解)](#3、使用 Json::FastWriter (已弃用,但需了解))

六、反序列化

[1、使用 Json::Reader](#1、使用 Json::Reader)

[2、使用 Json::CharReader 或 Json::parseFromStream (现代推荐方式)](#2、使用 Json::CharReader 或 Json::parseFromStream (现代推荐方式))

[七、核心类 Json::Value 常用操作](#七、核心类 Json::Value 常用操作)

1、构造函数

基本构造函数

便捷构造函数示例

2、访问元素

对象访问操作

数组访问操作

3、类型检查方法

基础类型检查

复合类型检查

4、赋值和类型转换

赋值操作符

类型转换方法

5、数组和对象操作

数组操作

对象操作

6、实用方法和最佳实践

遍历操作

深拷贝和比较

序列化和反序列化

7、错误处理和最佳实践

安全访问模式

异常处理

总结

八、总结与最佳实践

九、完整代码示例

[示例 1:创建和解析 JSON](#示例 1:创建和解析 JSON)

[示例 2:文件读写和错误处理](#示例 2:文件读写和错误处理)

[示例 3:高级操作和类型检查](#示例 3:高级操作和类型检查)

[十、核心 API 总结](#十、核心 API 总结)

主要类和方法

[Json::Value 主要方法](#Json::Value 主要方法)

构建器和读写器

十一、编译和测试


一、概述

JsonCpp 是一个开源的 C++ 第三方库,专门用于处理 JSON 数据格式。它提供了强大而便捷的功能,能够轻松地将内存中的 C++ 数据结构序列化为 JSON 字符串,或将 JSON 字符串反序列化回 C++ 数据结构。由于其出色的稳定性、易用性和性能,JsonCpp 被广泛应用于各种需要数据交换的 C++ 项目中,尤其是在网络通信和配置文件解析等场景,并经过了大量实践的验证。


二、核心特性

  1. 简单易用:提供了直观的、类似于操作标准容器的 API,使得创建、访问和修改 JSON 数据变得非常简单。Jsoncpp 提供了直观且一致的 API 设计,使得开发者能够快速上手并高效地处理 JSON 数据,无需深入了解复杂的底层实现。

  2. 高性能:库内部经过优化,能够高效地处理大量 JSON 数据的序列化与反序列化任务。

  3. 全面支持 :完整支持 JSON 标准定义的所有数据类型,包括对象(键值对集合)、数组(有序值序列)、字符串、数字(整数和浮点数)、布尔值以及 null 值。

  4. 强大健壮的错误处理:在解析 JSON 数据时,能提供详细的错误信息(包括错误类型和发生位置),极大地方便了开发过程中的调试。


**三、**安装 JsonCpp

在不同的 Linux 发行版上,可以使用包管理器轻松安装:

1、Ubuntu / Debian

bash 复制代码
sudo apt-get install libjsoncpp-dev

这里我已经安装过了,就不再赘述了

2、CentOS / RHEL / Fedora

bash 复制代码
sudo yum install jsoncpp-devel
# 或者使用 dnf (新版本 Fedora/CentOS)
# sudo dnf install jsoncpp-devel

3、编译时显示链接库

因为JsonCpp库是第三方库,所以编译时需要我们显示链接库:

bash 复制代码
g++ -o program program.cpp -ljsoncpp

实际上 g++ 在背后自动执行了:

  1. 编译 program.cpp 生成临时目标文件

  2. 自动链接临时目标文件 + libjsoncpp.so

  3. 清理临时文件,只保留最终可执行文件

为什么可以省略 -L 路径? 因为系统标准库路径(如 /usr/lib, /usr/lib/x86_64-linux-gnu)已经在链接器的默认搜索路径中。

4、查看是否已安装 JsonCpp

bash 复制代码
# 查看是否已安装
dpkg -l | grep jsoncpp
bash 复制代码
# 或者使用 apt
apt list --installed | grep jsoncpp

5、主要类介绍

  • Json::Value - 所有 JSON 值的容器

  • Json::Reader - 用于解析 JSON 字符串(旧版)

  • Json::CharReaderBuilder - 用于构建 JSON 解析器(新版)

  • Json::StreamWriterBuilder - 用于构建 JSON 序列化器


四、JSON 数据格式转化

1、JSON 的数据格式

JSON(JavaScript Object Notation)主要有两种结构化的数据格式,以及六种基本的数据类型。

两种主要结构:

对象

  • 格式 :使用花括号 {} 包裹,内容是由逗号分隔的 键值对

  • :必须是字符串,用双引号包裹。

  • :可以是任何 JSON 数据类型。

  • 示例

    cpp 复制代码
    {
      "name": "张三",
      "age": 30,
      "isStudent": false
    }

数组

  • 格式 :使用方括号 [] 包裹,内容是由逗号分隔的 值列表

  • :可以是任何 JSON 数据类型,并且数组中的元素类型可以不同。

  • 示例

    cpp 复制代码
    ["apple", "banana", "cherry"]

    或包含混合类型和对象:

    cpp 复制代码
    [1, "hello", true, {"city": "Beijing"}]

六种基本数据类型:

  • 字符串 :必须使用双引号 "" 包裹。例如:"Hello, World!"

  • 数字 :整数或浮点数,不加引号 。例如:423.14

  • 布尔值truefalse(小写,不加引号)。

  • 空值null(不加引号)。

  • 对象:如上所述。

  • 数组:如上所述。

"JSON字符串"是什么?"JSON 字符串"通常有两种含义:

  1. 指代 JSON 数据本身的一种数据类型 ,即用双引号括起来的文本,如上文中的 "name""张三"

  2. 更常见的,指一个完整的、符合 JSON 格式的文本字符串。这个字符串可能表示一个对象、一个数组或任何其他有效的 JSON 值。它是在网络传输或文件存储时 JSON 数据的表现形式。

示例:一个完整的"JSON字符串"

cpp 复制代码
// 这是一个表示对象的 JSON 字符串
"{\"name\":\"张三\", \"age\":30, \"hobbies\":[\"reading\", \"coding\"]}"

在 C++ 代码中,你通常会用 std::string 来存储这样的字符串。

2、序列化与反序列化

这两个过程是数据交换的核心,方向完全相反。

1. 序列化

  • 是什么 :将 内存中的数据结构或对象 转换为 可以存储或传输的格式(如 JSON 字符串) 的过程。

  • 转换关系内存对象 → 字符串

  • 目的:为了将数据保存到文件,或通过网络发送给另一个程序。

  • 在 JsonCpp 中的例子 :将 Json::Value 这个内存中的对象,转换成 std::string

    cpp 复制代码
    Json::Value root;
    root["name"] = "Joe";
    root["age"] = 25;
    
    // 序列化:将内存中的 root 对象转换为 JSON 字符串
    std::string json_str = root.toStyledString(); // 现在 json_str 是 "{\"name\":\"Joe\", \"age\":25}" 的字符串

2. 反序列化

  • 是什么 :将 序列化后的格式(如 JSON 字符串) 重新转换回 内存中的数据结构或对象 的过程。

  • 转换关系字符串 → 内存对象

  • 目的:将从文件或网络接收到的数据,重新构建成程序可以理解和操作的内存对象。

  • 在 JsonCpp 中的例子 :将 std::string 解析成 Json::Value 对象。

    cpp 复制代码
    std::string received_json = "{\"name\":\"Joe\", \"age\":25}";
    Json::Value root;
    Json::Reader reader;
    
    // 反序列化:将 JSON 字符串 received_json 解析回内存中的 root 对象
    reader.parse(received_json, root);
    
    // 现在可以从 root 对象中像访问普通变量一样获取数据了
    std::string name = root["name"].asString(); // name = "Joe"
    int age = root["age"].asInt(); // age = 25

总结

概念 方向 在 JsonCpp 中的表现 目的
序列化 内存对象 → JSON 字符串 Json::Valuestd::string 存储、传输
反序列化 JSON 字符串 → 内存对象 std::stringJson::Value 读取、使用数据

简单来说:

  • 序列化 就是 "打包" 数据,准备发出去。

  • 反序列化 就是 "拆包" 数据,拿来自己用。


五、序列化

序列化是将内存中的数据结构或对象转换为可存储或传输的格式(如 JSON 字符串)的过程,以便进行网络传输或文件存储。Jsoncpp 提供了多种序列化方式,满足不同场景的需求。

1、使用 Json::Value::toStyledString 方法

优点: 最简单直接,直接将 Json::Value 对象转换为格式化的 JSON 字符串,比较美观,包含缩进和换行,非常适合人类阅读和调试。

**缺点:**会包含不必要的空格和换行,数据体积较大,不适合在生产环境中进行网络传输。

示例:

cpp 复制代码
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h> // 包含头文件

int main() {
    Json::Value root;
    root["name"] = "joe";
    root["sex"] = "男";
    
    std::string json_str = root.toStyledString(); // 序列化为格式化的字符串
    std::cout << json_str << std::endl;
    return 0;
}

输出:

这段代码演示了使用 JsonCpp 库来创建和序列化 JSON 数据。让我们逐行分析:

1. 头文件包含

cpp 复制代码
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h> // JSON 库头文件
  • iostream: 用于输入输出

  • string: 用于字符串处理

  • jsoncpp/json/json.h: JsonCpp 库的主要头文件

2. 创建 JSON 对象

cpp 复制代码
Json::Value root;  // 创建根节点

Json::Value 是 JsonCpp 的核心类,可以表示任何 JSON 类型(对象、数组、字符串、数字等)。

3. 添加数据

cpp 复制代码
root["name"] = "joe";   // 添加字符串字段
root["sex"] = "男";     // 添加另一个字符串字段
  • 使用 [] 操作符向 JSON 对象添加键值对

  • 自动将 C++ 字符串转换为 JSON 字符串

4. 序列化为字符串

cpp 复制代码
std::string json_str = root.toStyledString();

toStyledString() 方法将 JSON 对象转换为格式化的字符串,包含缩进和换行,便于阅读。

5. 输出结果

cpp 复制代码
std::cout << json_str << std::endl;

输出序列化后的 JSON 字符串。

2、使用 Json::StreamWriterBuilder (现代推荐方式)

优点:

  • 替代了旧的 StreamWriter,提供了工厂模式,可以定制输出格式(如缩进量),是更灵活且面向未来的方式。

  • 提供高度定制化的序列化选项

  • 可控制缩进、换行符等格式细节

  • 适合需要精细控制输出格式的场景

示例:

cpp 复制代码
#include <iostream>
#include <string>
#include <sstream>
#include <memory>
#include <jsoncpp/json/json.h>

int main() {
    Json::Value root;
    root["name"] = "joe";
    root["sex"] = "男";
    
    Json::StreamWriterBuilder writer_builder;
    // 可以通过 writer_builder["indentation"] = "" 来取消缩进,生成紧凑JSON
    std::unique_ptr<Json::StreamWriter> writer(writer_builder.newStreamWriter());
    
    std::ostringstream oss;
    writer->write(root, &oss);
    std::cout << oss.str() << std::endl;
    return 0;
}

输出:

这段代码展示了使用 JsonCpp 的流式写入器来序列化 JSON 数据。让我们逐部分分析:

1. 头文件包含

cpp 复制代码
#include <iostream>
#include <string>
#include <sstream>
#include <memory>
#include <jsoncpp/json/json.h>

新增的头文件:

  • sstream: 用于字符串流操作

  • memory: 用于智能指针管理

2. 创建 JSON 对象

cpp 复制代码
Json::Value root;
root["name"] = "joe";
root["sex"] = "男";

与之前相同,创建包含基本数据的 JSON 对象。

3. 配置流式写入器

cpp 复制代码
Json::StreamWriterBuilder writer_builder;

StreamWriterBuilder 是一个工厂类,用于创建和配置 JSON 写入器。

4. 创建写入器实例

cpp 复制代码
std::unique_ptr<Json::StreamWriter> writer(writer_builder.newStreamWriter());
  • 使用智能指针 unique_ptr 自动管理内存

  • newStreamWriter() 创建新的流式写入器

5. 序列化到字符串流

cpp 复制代码
std::ostringstream oss;
writer->write(root, &oss);
  • ostringstream 用于在内存中构建字符串

  • write() 方法将 JSON 对象写入输出流

6. 输出结果

cpp 复制代码
std::cout << oss.str() << std::endl;

从字符串流中获取最终字符串并输出。

配置选项:代码注释中提到的配置方法

cpp 复制代码
// 生成紧凑格式(无缩进)
writer_builder["indentation"] = "";

// 其他常用配置
writer_builder["commentStyle"] = "None";  // 不输出注释
writer_builder["enableYAMLCompatibility"] = false;  // 禁用YAML兼容

3、使用 Json::FastWriter (已弃用,但需了解)

优点 :生成紧凑的、不带任何多余空格的 JSON 字符串,体积最小,传输效率最高。比 StyledWriter 更快,因为它不会添加多余的空格和换行符。

  • 追求极致性能,不添加任何额外格式(如空格、换行符)

  • 生成的 JSON 字符串最为紧凑

  • 适合对性能要求高且对可读性要求不高的场景

缺点该类在现代 Jsoncpp 版本中已被标记为弃用 ,建议使用 StreamWriterBuilder 并设置 "indentation" 为空字符串来达到同样效果。

示例代码

cpp 复制代码
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>

int main() {
    Json::Value root;
    root["name"] = "joe";
    root["sex"] = "男";
    
    Json::FastWriter writer;
    std::string jsonStr = writer.write(root);
    
    std::cout << jsonStr << std::endl;
    
    return 0;
}

输出结果

这段代码展示了使用 JsonCpp 的 FastWriter 来快速序列化 JSON 数据。让我们逐部分分析:

1. 头文件包含

cpp 复制代码
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>

基础的头文件,不需要额外的流或内存管理头文件。

2. 创建 JSON 对象

cpp 复制代码
Json::Value root;
root["name"] = "joe";
root["sex"] = "男";

与之前相同,创建包含基本数据的 JSON 对象。

3. 使用 FastWriter 序列化

cpp 复制代码
Json::FastWriter writer;
std::string jsonStr = writer.write(root);
  • FastWriter 专门用于生成紧凑格式的 JSON

  • write() 方法直接将 JSON 对象转换为字符串

4. 输出结果

cpp 复制代码
std::cout << jsonStr << std::endl;

对比 StyledWriter

cpp 复制代码
// 使用 StyledWriter 的示例
Json::StyledWriter writer;
std::string jsonStr = writer.write(root);

StyledWriter 的特性:

  1. 美化输出:自动添加缩进、换行,提高可读性

  2. 固定格式:输出格式是固定的,无法自定义

  3. 适合调试:非常适合开发阶段查看 JSON 结构


六、反序列化

反序列化是将序列化的 JSON 字符串重新转换为内存中的数据结构或对象的过程。Jsoncpp 提供了强大的反序列化功能,支持从字符串或流中解析 JSON 数据。

1、使用 Json::Reader

特点:

  • 提供详细的错误信息和精确的位置指示

  • 支持从字符串或流中解析 JSON 数据

  • 是 Jsoncpp 中最常用的反序列化工具

示例代码:

cpp 复制代码
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>

int main() {
    std::string jsonStr = "{\"name\":\"张三\", \"age\":30, \"city\":\"北京\"}";
    
    Json::Reader reader;
    Json::Value root;
    
    bool success = reader.parse(jsonStr, root);
    
    if (!success) {
        std::cerr << "解析失败: " << reader.getFormattedErrorMessages() << std::endl;
        return 1;
    }
    
    std::string name = root["name"].asString();
    int age = root["age"].asInt();
    std::string city = root["city"].asString();
    
    std::cout << "姓名: " << name << std::endl;
    std::cout << "年龄: " << age << std::endl;
    std::cout << "城市: " << city << std::endl;
    
    return 0;
}

输出结果:

这段代码演示了使用 JsonCpp 库解析 JSON 字符串的过程。让我们详细分析每个部分:

1. 头文件包含

cpp 复制代码
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>
  • 包含必要的输入输出、字符串处理和 JSON 解析头文件

2. 定义 JSON 字符串

cpp 复制代码
std::string jsonStr = "{\"name\":\"张三\", \"age\":30, \"city\":\"北京\"}";

这是一个包含三个字段的 JSON 对象字符串:

  • name: 字符串类型,值为"张三"

  • age: 数字类型,值为30

  • city: 字符串类型,值为"北京"

注意:C++ 中需要转义双引号,所以使用 \"

3. 创建 JSON 解析器

cpp 复制代码
Json::Reader reader;
Json::Value root;
  • Json::Reader: JSON 解析器类,负责将字符串解析为 JSON 对象

  • Json::Value: 用于存储解析后的 JSON 数据

4. 解析 JSON 字符串

cpp 复制代码
bool success = reader.parse(jsonStr, root);

parse() 方法执行解析:

  • 参数1: 要解析的 JSON 字符串

  • 参数2: 存储解析结果的 Json::Value 对象

  • 返回值: bool 类型,表示解析是否成功

5. 错误处理

cpp 复制代码
if (!success) {
    std::cerr << "解析失败: " << reader.getFormattedErrorMessages() << std::endl;
    return 1;
}
  • 如果解析失败,输出格式化的错误信息

  • getFormattedErrorMessages() 提供详细的错误描述

6. 提取数据

cpp 复制代码
std::string name = root["name"].asString();
int age = root["age"].asInt();
std::string city = root["city"].asString();

使用 [] 操作符访问字段,并用对应的方法转换类型:

  • asString(): 转换为 std::string

  • asInt(): 转换为 int

  • 其他常用方法:asDouble(), asBool(), asUInt()

7. 输出结果

cpp 复制代码
std::cout << "姓名: " << name << std::endl;
std::cout << "年龄: " << age << std::endl;
std::cout << "城市: " << city << std::endl;

2、使用 Json::CharReaderJson::parseFromStream (现代推荐方式)

Json::Reader 类也已逐渐被新的接口取代。如下:

**优点:**提供了强大的错误处理机制,能准确报告解析失败的原因和位置。

适用场景:需要更精细地控制解析过程、实现自定义的解析逻辑

说明 :虽然 Json::CharReader 提供了更底层的解析控制,但在大多数情况下,使用 Json::ReaderparseFromStream 函数已经足够。

示例:

版本一:使用 Json::CharReader

cpp 复制代码
#include <iostream>
#include <memory>
#include <string>
#include <sstream>
#include <jsoncpp/json/json.h>

int main() {
    // 待解析的 JSON 字符串
    std::string json_string = R"({"name":"张三", "age":30, "city":"北京"})";
    
    Json::Value root;
    Json::CharReaderBuilder reader_builder;
    std::unique_ptr<Json::CharReader> reader(reader_builder.newCharReader());
    
    // 执行解析
    std::string errors;
    bool parsing_ok = reader->parse(json_string.c_str(), 
                                   json_string.c_str() + json_string.length(), 
                                   &root, &errors);
    
    if (!parsing_ok) {
        std::cout << "Failed to parse JSON: " << errors << std::endl;
        return 1;
    }
    
    // 访问解析后的数据
    std::string name = root.isMember("name") ? root["name"].asString() : "Unknown";
    int age = root.isMember("age") ? root["age"].asInt() : 0;
    std::string city = root.isMember("city") ? root["city"].asString() : "Unknown";
    
    std::cout << "Name: " << name << std::endl;
    std::cout << "Age: " << age << std::endl;
    std::cout << "City: " << city << std::endl;
    
    return 0;
}

输出:

这段代码展示了使用 JsonCpp 的现代解析方式,采用了 CharReaderBuilder 和智能指针。让我们详细分析:

1. 头文件包含

cpp 复制代码
#include <iostream>
#include <memory>
#include <string>
#include <sstream>
#include <jsoncpp/json/json.h>

新增了:

  • memory: 用于智能指针 std::unique_ptr

  • sstream: 虽然代码中未直接使用,但通常用于字符串流操作

2. 使用原始字符串字面量

cpp 复制代码
std::string json_string = R"({"name":"张三", "age":30, "city":"北京"})";

与之前相同,使用原始字符串避免转义。

3. 创建现代 JSON 解析器

cpp 复制代码
Json::Value root;
Json::CharReaderBuilder reader_builder;
std::unique_ptr<Json::CharReader> reader(reader_builder.newCharReader());

关键改进:

  • Json::CharReaderBuilder: 工厂类,用于创建和配置解析器

  • std::unique_ptr: 智能指针,自动管理内存,避免内存泄漏

  • 这是 JsonCpp 推荐的现代解析方式

4. 执行解析

cpp 复制代码
std::string errors;
bool parsing_ok = reader->parse(json_string.c_str(),
                               json_string.c_str() + json_string.length(),
                               &root, &errors);

参数详解:

  • json_string.c_str(): JSON 字符串的起始位置

  • json_string.c_str() + json_string.length(): JSON 字符串的结束位置

  • &root: 存储解析结果的 Json::Value 对象

  • &errors: 存储错误信息的字符串

5. 错误处理

cpp 复制代码
if (!parsing_ok) {
    std::cout << "Failed to parse JSON: " << errors << std::endl;
    return 1;
}

使用独立的 errors 字符串接收错误信息。

6. 安全的数据访问

cpp 复制代码
std::string name = root.isMember("name") ? root["name"].asString() : "Unknown";
int age = root.isMember("age") ? root["age"].asInt() : 0;
std::string city = root.isMember("city") ? root["city"].asString() : "Unknown";

保持安全的数据访问模式。

版本二:使用 Json::parseFromStream

cpp 复制代码
#include <iostream>
#include <string>
#include <sstream>
#include <jsoncpp/json/json.h>

int main() {
    // 待解析的 JSON 字符串
    std::string json_string = R"({"name":"张三", "age":30, "city":"北京"})";
    
    Json::Value root;
    std::stringstream ss(json_string);  // 将字符串转换为流
    Json::CharReaderBuilder reader_builder;
    std::string errors;
    
    // 执行解析
    bool parsing_ok = Json::parseFromStream(reader_builder, ss, &root, &errors);
    
    if (!parsing_ok) {
        std::cout << "Failed to parse JSON: " << errors << std::endl;
        return 1;
    }
    
    // 访问解析后的数据
    std::string name = root.isMember("name") ? root["name"].asString() : "Unknown";
    int age = root.isMember("age") ? root["age"].asInt() : 0;
    std::string city = root.isMember("city") ? root["city"].asString() : "Unknown";
    
    std::cout << "Name: " << name << std::endl;
    std::cout << "Age: " << age << std::endl;
    std::cout << "City: " << city << std::endl;
    
    return 0;
}

输出:

这段代码展示了使用 JsonCpp 的流式解析方式,这是最现代和推荐的 JSON 解析方法。让我们详细分析:

1. 头文件包含

cpp 复制代码
#include <iostream>
#include <string>
#include <sstream>
#include <jsoncpp/json/json.h>
  • sstream: 用于字符串流操作,这是本方法的核心

2. 创建字符串流

cpp 复制代码
std::string json_string = R"({"name":"张三", "age":30, "city":"北京"})";
Json::Value root;
std::stringstream ss(json_string);  // 将字符串转换为流

关键改进:

  • 使用 std::stringstream 将 JSON 字符串包装为流

  • 流式处理更适合大型数据和处理文件

3. 创建解析器配置

cpp 复制代码
Json::CharReaderBuilder reader_builder;
std::string errors;
  • CharReaderBuilder: 解析器工厂,可以配置解析选项

  • errors: 用于接收错误信息

4. 流式解析

cpp 复制代码
bool parsing_ok = Json::parseFromStream(reader_builder, ss, &root, &errors);

parseFromStream 参数详解:

  • reader_builder: 解析器配置

  • ss: 输入流(可以是文件流、字符串流等)

  • &root: 存储解析结果的 Json::Value 对象

  • &errors: 存储错误信息

5. 错误处理和数据访问

与之前相同,保持安全的数据访问模式。

总结:

  • toStyledString、StreamWriter 和 FastWriter 提供了多种序列化方案,可根据实际需求灵活选用

  • Json::Reader 和 parseFromStream 是 Jsoncpp 的核心反序列化工具,具备完善的错误处理能力

  • 进行序列化/反序列化操作时,务必全面处理错误情况并严格验证输入输出的有效性


七、核心类 Json::Value 常用操作

Json::Value 是 Jsoncpp 库的核心类,用于表示和操作 JSON 数据结构,它是一个通用的、自描述的数据类型,可以表示任何 JSON 数据。它提供了丰富的接口来创建、访问和修改 JSON 数据。

1、构造函数

基本构造函数

cpp 复制代码
// 默认构造函数,创建 null 类型的 Json::Value
Json::Value();

// 根据指定类型创建 Json::Value
Json::Value(ValueType type, bool allocated = false);

ValueType 枚举值:

  • nullValue: null 值

  • intValue: 整数值

  • uintValue: 无符号整数值

  • realValue: 浮点数值

  • stringValue: 字符串值

  • booleanValue: 布尔值

  • arrayValue: 数组类型

  • objectValue: 对象类型

便捷构造函数示例

cpp 复制代码
Json::Value nullVal;                    // null
Json::Value intVal(42);                 // 整数
Json::Value doubleVal(3.14);            // 浮点数
Json::Value boolVal(true);              // 布尔值
Json::Value stringVal("hello");         // 字符串

2、访问元素

对象访问操作

cpp 复制代码
// 通过键访问对象元素(不存在时自动创建)
Json::Value& operator[](const char* key);
Json::Value& operator[](const std::string& key);

// 安全访问(不存在时抛出异常)
Json::Value& at(const char* key);
Json::Value& at(const std::string& key);

使用示例:

cpp 复制代码
Json::Value obj;
obj["name"] = "Alice";           // 自动创建
obj["age"] = 25;

// 安全访问
try {
    std::string name = obj.at("name").asString();
} catch (const std::exception& e) {
    // 处理键不存在的异常
}

数组访问操作

cpp 复制代码
// 通过索引访问数组元素
Json::Value& operator[](ArrayIndex index);

// 安全访问数组元素
Json::Value& at(ArrayIndex index);

使用示例:

cpp 复制代码
Json::Value arr;
arr[0] = "first";
arr[1] = "second";

// 注意:如果索引超出范围会创建新元素

3、类型检查方法

基础类型检查

cpp 复制代码
bool isNull() const;        // 是否为 null
bool isBool() const;        // 是否为布尔值
bool isInt() const;         // 是否为 int
bool isInt64() const;       // 是否为 int64
bool isUInt() const;        // 是否为 unsigned int
bool isUInt64() const;      // 是否为 uint64
bool isDouble() const;      // 是否为 double
bool isString() const;      // 是否为字符串

复合类型检查

cpp 复制代码
bool isArray() const;       // 是否为数组
bool isObject() const;      // 是否为对象
bool isNumeric() const;     // 是否为数字类型
bool isIntegral() const;    // 是否为整数类型

使用示例:

cpp 复制代码
Json::Value val = 42;

if (val.isInt()) {
    std::cout << "这是一个整数" << std::endl;
}

if (val.isNumeric()) {
    std::cout << "这是一个数字" << std::endl;
}

4、赋值和类型转换

赋值操作符

cpp 复制代码
Json::Value& operator=(bool value);
Json::Value& operator=(int value);
Json::Value& operator=(unsigned int value);
Json::Value& operator=(Int64 value);
Json::Value& operator=(UInt64 value);
Json::Value& operator=(double value);
Json::Value& operator=(const char* value);
Json::Value& operator=(const std::string& value);

类型转换方法

cpp 复制代码
bool asBool() const;                    // 转换为 bool
int asInt() const;                      // 转换为 int
Int64 asInt64() const;                  // 转换为 int64
unsigned int asUInt() const;            // 转换为 unsigned int
UInt64 asUInt64() const;                // 转换为 uint64
double asDouble() const;                // 转换为 double
std::string asString() const;           // 转换为 string

重要注意事项:

  • 类型转换可能会失败,建议先进行类型检查

  • 对于不兼容的类型转换,JsonCpp 会尝试进行合理的转换

使用示例:

cpp 复制代码
Json::Value val = "123";

if (val.isString() && val.isNumeric()) {
    int num = val.asInt();  // 字符串 "123" 可以转换为数字 123
}

5、数组和对象操作

数组操作

cpp 复制代码
size_t size() const;                    // 获取元素数量
bool empty() const;                     // 检查是否为空
void resize(ArrayIndex newSize);        // 调整数组大小
void clear();                           // 清空所有元素
void append(const Json::Value& value);  // 添加元素到末尾

数组操作示例:

cpp 复制代码
Json::Value arr;
arr.append("first");
arr.append("second");
arr.append(Json::Value(42));

std::cout << "数组大小: " << arr.size() << std::endl;

// 调整数组大小
arr.resize(5);

注意:

Json::Value arr; 创建的并不是"JSON数组的数组",而是一个可以包含任意类型元素的动态数组 ,类似于其他语言中的 List<object>any[]。在 Json::Value 数组中,元素只有值,没有键 。数组是通过数字索引来访问的。

对象操作

cpp 复制代码
// 带默认值的访问
Json::Value& operator[](const char* key, const Json::Value& defaultValue);
Json::Value& operator[](const std::string& key, const Json::Value& defaultValue);

// 成员检查
bool isMember(const char* key) const;
bool isMember(const std::string& key) const;

// 获取所有键
std::vector<std::string> getMemberNames() const;

对象操作示例:

cpp 复制代码
Json::Value obj;
obj["name"] = "Alice";
obj["age"] = 25;

// 检查成员是否存在
if (obj.isMember("name")) {
    std::cout << "name 存在" << std::endl;
}

// 获取所有键名
std::vector<std::string> keys = obj.getMemberNames();

6、实用方法和最佳实践

遍历操作

cpp 复制代码
// 遍历对象
Json::Value obj;
for (const auto& key : obj.getMemberNames()) {
    std::cout << key << ": " << obj[key].asString() << std::endl;
}

// 遍历数组
Json::Value arr;
for (Json::ArrayIndex i = 0; i < arr.size(); ++i) {
    std::cout << arr[i].asString() << std::endl;
}

深拷贝和比较

cpp 复制代码
// 深拷贝
Json::Value deepCopy() const;

// 比较操作
bool operator==(const Json::Value& other) const;
bool operator!=(const Json::Value& other) const;

序列化和反序列化

cpp 复制代码
// 转换为字符串
std::string toStyledString() const;

// 紧凑格式输出
Json::FastWriter writer;
std::string jsonStr = writer.write(obj);

// 从字符串解析
Json::Reader reader;
Json::Value root;
bool parsingSuccessful = reader.parse(jsonStr, root);

7、错误处理和最佳实践

安全访问模式

cpp 复制代码
// 推荐的安全访问方式
int getSafeInt(const Json::Value& val, const std::string& key, int defaultValue = 0) {
    if (val.isObject() && val.isMember(key) && val[key].isInt()) {
        return val[key].asInt();
    }
    return defaultValue;
}

异常处理

cpp 复制代码
try {
    Json::Value obj;
    std::string value = obj.at("nonexistent").asString();
} catch (const Json::LogicError& e) {
    std::cerr << "JSON 逻辑错误: " << e.what() << std::endl;
} catch (const std::exception& e) {
    std::cerr << "其他错误: " << e.what() << std::endl;
}

总结

Json::Value 提供了完整的 JSON 数据处理能力,主要特点包括:

  1. 类型安全:严格的数据类型检查和转换

  2. 灵活访问:支持多种访问方式,兼顾便利性和安全性

  3. 完整功能:涵盖 JSON 标准的所有操作需求

  4. 良好性能:在保证功能完整性的同时提供较好的性能

在实际使用中,建议根据具体场景选择合适的访问方式,并始终进行适当的错误处理,以确保代码的健壮性。


八、总结与最佳实践

  • 序列化选择

    • 调试/日志输出 :使用 toStyledString()

    • 网络传输/存储 :使用 StreamWriterBuilder(可配置为紧凑模式)。

  • 反序列化选择 :使用 CharReaderBuilderparse 方法,并务必检查返回值和处理错误

  • 安全第一 :在通过 asXxx() 获取值前,养成使用 isXxx()get(key, default) 进行类型检查和提供默认值的习惯,这样可以构建出更健壮的程序。

  • 头文件 :现代 Jsoncpp 推荐使用 #include <jsoncpp/json/json.h>


九、完整代码示例

示例 1:创建和解析 JSON

cpp 复制代码
#include <iostream>
#include <sstream>
#include <string>
#include <jsoncpp/json/json.h>

// 编译命令:g++ -o example1 example1.cpp -ljsoncpp

int main() {
    // 创建一个 JSON 对象
    Json::Value root;
    
    // 添加不同类型的数据
    root["name"] = "张三";
    root["age"] = 25;
    root["is_student"] = false;
    
    // 添加数组
    Json::Value languages;
    languages.append("C++");
    languages.append("Python");
    languages.append("Java");
    root["languages"] = languages;
    
    // 添加嵌套对象
    Json::Value address;
    address["city"] = "北京";
    address["street"] = "长安街";
    address["zipcode"] = "100000";
    root["address"] = address;
    
    // 将 JSON 对象序列化为字符串
    Json::StreamWriterBuilder writer;
    std::string json_string = Json::writeString(writer, root);
    
    std::cout << "生成的 JSON 字符串:" << std::endl;
    std::cout << json_string << std::endl;
    
    // 解析 JSON 字符串
    Json::Value parsed_root;
    Json::CharReaderBuilder reader;
    std::string errors;
    
    std::istringstream s(json_string);
    bool parsingSuccessful = Json::parseFromStream(reader, s, &parsed_root, &errors);
    
    if (parsingSuccessful) {
        std::cout << "\n解析成功!" << std::endl;
        std::cout << "姓名: " << parsed_root["name"].asString() << std::endl;
        std::cout << "年龄: " << parsed_root["age"].asInt() << std::endl;
        std::cout << "是否是学生: " << parsed_root["is_student"].asBool() << std::endl;
        
        // 遍历数组
        std::cout << "编程语言: ";
        const Json::Value& langs = parsed_root["languages"];
        for (int i = 0; i < langs.size(); i++) {
            std::cout << langs[i].asString() << " ";
        }
        std::cout << std::endl;
        
        // 访问嵌套对象
        std::cout << "城市: " << parsed_root["address"]["city"].asString() << std::endl;
    } else {
        std::cout << "解析失败: " << errors << std::endl;
    }
    
    return 0;
}

输出:

代码讲解:

  • Json::Value 是一个通用的容器,可以存储各种类型的 JSON 数据

  • 使用 operator[] 来添加或访问对象属性

  • append() 方法用于向数组添加元素

  • Json::StreamWriterBuilder 用于将 JSON 对象序列化为字符串

  • Json::CharReaderBuilder 用于从字符串解析 JSON

  • asString(), asInt(), asBool() 等方法用于类型转换

示例 2:文件读写和错误处理

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

// 编译命令:g++ -o example2 example2.cpp -ljsoncpp

// 写入 JSON 到文件
bool writeJsonToFile(const std::string& filename, const Json::Value& root) {
    Json::StreamWriterBuilder writer;
    writer["indentation"] = "    ";  // 设置缩进为4个空格
    
    std::ofstream file(filename);
    if (!file.is_open()) {
        std::cerr << "无法打开文件: " << filename << std::endl;
        return false;
    }
    
    file << Json::writeString(writer, root);
    file.close();
    return true;
}

// 从文件读取 JSON
Json::Value readJsonFromFile(const std::string& filename) {
    Json::Value root;
    Json::CharReaderBuilder reader;
    std::string errors;
    
    std::ifstream file(filename);
    if (!file.is_open()) {
        std::cerr << "无法打开文件: " << filename << std::endl;
        return root;  // 返回空的 Value
    }
    
    bool parsingSuccessful = Json::parseFromStream(reader, file, &root, &errors);
    if (!parsingSuccessful) {
        std::cerr << "解析 JSON 失败: " << errors << std::endl;
    }
    
    return root;
}

int main() {
    // 创建复杂的 JSON 结构
    Json::Value company;
    company["company_name"] = "科技公司";
    company["established"] = 2010;
    
    // 员工数组
    Json::Value employees;
    
    // 第一个员工
    Json::Value emp1;
    emp1["id"] = 1001;
    emp1["name"] = "李四";
    emp1["department"] = "研发部";
    emp1["salary"] = 15000.50;
    emp1["skills"].append("C++");
    emp1["skills"].append("Linux");
    emp1["skills"].append("数据库");
    
    // 第二个员工
    Json::Value emp2;
    emp2["id"] = 1002;
    emp2["name"] = "王五";
    emp2["department"] = "市场部";
    emp2["salary"] = 12000.75;
    emp2["skills"].append("市场营销");
    emp2["skills"].append("英语");
    
    employees.append(emp1);
    employees.append(emp2);
    company["employees"] = employees;
    
    // 项目信息
    Json::Value projects;
    projects["ongoing"] = 5;
    projects["completed"] = 12;
    projects["revenue"] = 2500000.80;
    company["projects"] = projects;
    
    // 写入文件
    std::string filename = "company_data.json";
    if (writeJsonToFile(filename, company)) {
        std::cout << "JSON 数据已写入文件: " << filename << std::endl;
    }
    
    // 从文件读取
    Json::Value loaded_data = readJsonFromFile(filename);
    if (!loaded_data.isNull()) {
        std::cout << "\n从文件读取的数据:" << std::endl;
        std::cout << "公司名称: " << loaded_data["company_name"].asString() << std::endl;
        std::cout << "成立年份: " << loaded_data["established"].asInt() << std::endl;
        
        // 遍历员工信息
        std::cout << "\n员工信息:" << std::endl;
        const Json::Value& emps = loaded_data["employees"];
        for (Json::ArrayIndex i = 0; i < emps.size(); i++) {
            std::cout << "员工 " << (i + 1) << ":" << std::endl;
            std::cout << "  ID: " << emps[i]["id"].asInt() << std::endl;
            std::cout << "  姓名: " << emps[i]["name"].asString() << std::endl;
            std::cout << "  部门: " << emps[i]["department"].asString() << std::endl;
            std::cout << "  薪资: " << emps[i]["salary"].asFloat() << std::endl;
            
            std::cout << "  技能: ";
            const Json::Value& skills = emps[i]["skills"];
            for (Json::ArrayIndex j = 0; j < skills.size(); j++) {
                std::cout << skills[j].asString();
                if (j < skills.size() - 1) std::cout << ", ";
            }
            std::cout << std::endl << std::endl;
        }
        
        // 项目信息
        std::cout << "进行中项目: " << loaded_data["projects"]["ongoing"].asInt() << std::endl;
        std::cout << "已完成项目: " << loaded_data["projects"]["completed"].asInt() << std::endl;
        std::cout << "总收入: " << loaded_data["projects"]["revenue"].asDouble() << std::endl;
    }
    
    return 0;
}

示例 3:高级操作和类型检查

cpp 复制代码
#include <iostream>
#include <vector>
#include <jsoncpp/json/json.h>

// 编译命令:g++ -o example3 example3.cpp -ljsoncpp

// 检查 JSON 值的类型
void checkValueType(const Json::Value& value, const std::string& name) {
    std::cout << name << " 的类型: ";
    if (value.isNull()) std::cout << "Null";
    else if (value.isBool()) std::cout << "Bool";
    else if (value.isInt()) std::cout << "Int";
    else if (value.isUInt()) std::cout << "UInt";
    else if (value.isDouble()) std::cout << "Double";
    else if (value.isString()) std::cout << "String";
    else if (value.isArray()) std::cout << "Array";
    else if (value.isObject()) std::cout << "Object";
    std::cout << std::endl;
}

// 安全获取值(带默认值)
template<typename T>
T getValueSafe(const Json::Value& root, const std::string& key, T defaultValue) {
    if (root.isMember(key) && !root[key].isNull()) {
        if constexpr (std::is_same_v<T, std::string>) {
            return root[key].asString();
        } else if constexpr (std::is_same_v<T, int>) {
            return root[key].asInt();
        } else if constexpr (std::is_same_v<T, double>) {
            return root[key].asDouble();
        } else if constexpr (std::is_same_v<T, bool>) {
            return root[key].asBool();
        }
    }
    return defaultValue;
}

int main() {
    Json::Value config;
    
    // 设置各种类型的值
    config["app_name"] = "我的应用";
    config["version"] = "1.0.0";
    config["port"] = 8080;
    config["debug_mode"] = true;
    config["timeout"] = 30.5;
    config["database"]["host"] = "localhost";
    config["database"]["port"] = 3306;
    
    // 空值
    config["optional_setting"] = Json::nullValue;
    
    // 检查类型
    std::cout << "=== 类型检查 ===" << std::endl;
    checkValueType(config["app_name"], "app_name");
    checkValueType(config["port"], "port");
    checkValueType(config["debug_mode"], "debug_mode");
    checkValueType(config["timeout"], "timeout");
    checkValueType(config["optional_setting"], "optional_setting");
    checkValueType(config["database"], "database");
    
    // 安全获取值
    std::cout << "\n=== 安全获取值 ===" << std::endl;
    std::string app_name = getValueSafe(config, "app_name", std::string("默认应用"));
    int port = getValueSafe(config, "port", 80);
    double timeout = getValueSafe(config, "timeout", 60.0);
    std::string missing_value = getValueSafe(config, "missing_key", std::string("默认值"));
    
    std::cout << "应用名称: " << app_name << std::endl;
    std::cout << "端口: " << port << std::endl;
    std::cout << "超时: " << timeout << std::endl;
    std::cout << "缺失的值: " << missing_value << std::endl;
    
    // 遍历对象的所有成员
    std::cout << "\n=== 遍历所有成员 ===" << std::endl;
    Json::Value::Members members = config.getMemberNames();
    for (const auto& key : members) {
        std::cout << "键: " << key;
        std::cout << ", 类型: ";
        if (config[key].isString()) {
            std::cout << "字符串, 值: " << config[key].asString();
        } else if (config[key].isInt()) {
            std::cout << "整数, 值: " << config[key].asInt();
        } else if (config[key].isBool()) {
            std::cout << "布尔, 值: " << (config[key].asBool() ? "true" : "false");
        } else if (config[key].isDouble()) {
            std::cout << "浮点数, 值: " << config[key].asDouble();
        } else if (config[key].isObject()) {
            std::cout << "对象";
        } else if (config[key].isNull()) {
            std::cout << "空值";
        }
        std::cout << std::endl;
    }
    
    // 数组操作
    std::cout << "\n=== 数组操作 ===" << std::endl;
    Json::Value numbers;
    for (int i = 1; i <= 5; i++) {
        numbers.append(i * 10);
    }
    
    // 修改数组元素
    if (numbers.size() > 2) {
        numbers[2] = 999;  // 修改第三个元素
    }
    
    // 删除数组元素(通过重新创建)
    Json::Value new_numbers;
    for (Json::ArrayIndex i = 0; i < numbers.size(); i++) {
        if (i != 1) {  // 跳过第二个元素
            new_numbers.append(numbers[i]);
        }
    }
    
    std::cout << "修改后的数组: ";
    for (Json::ArrayIndex i = 0; i < new_numbers.size(); i++) {
        std::cout << new_numbers[i].asInt();
        if (i < new_numbers.size() - 1) std::cout << ", ";
    }
    std::cout << std::endl;
    
    return 0;
}

输出:


十、核心 API 总结

主要类和方法

Json::Value 主要方法

  • isNull(), isBool(), isInt(), isUInt(), isDouble(), isString(), isArray(), isObject() - 类型检查

  • asInt(), asUInt(), asInt64(), asUInt64(), asFloat(), asDouble(), asBool(), asString() - 类型转换

  • append(const Value& value) - 向数组添加元素

  • size() - 获取数组大小或对象成员数量

  • empty() - 检查是否为空

  • isMember(const char* key) - 检查对象是否包含指定键

  • getMemberNames() - 获取对象的所有键

  • operator[](const char* key) - 访问对象成员

  • operator[](Json::ArrayIndex index) - 访问数组元素

构建器和读写器

  • Json::StreamWriterBuilder - 构建 JSON 序列化器

  • Json::CharReaderBuilder - 构建 JSON 解析器

  • Json::writeString() - 将 JSON 对象序列化为字符串

  • Json::parseFromStream() - 从流中解析 JSON


十一、编译和测试

保存上述代码为 .cpp 文件后,使用以下命令编译:

cpp 复制代码
# 编译示例1
g++ -o example1 example1.cpp -ljsoncpp

# 编译示例2  
g++ -o example2 example2.cpp -ljsoncpp

# 编译示例3
g++ -o example3 example3.cpp -ljsoncpp

# 运行
./example1
./example2
./example3

运行后会生成 company_data.json 文件,内容格式化的 JSON 数据。

这些示例涵盖了 JsonCpp 的主要功能,包括创建、解析、文件读写、类型检查、安全访问等常用操作。

相关推荐
ao_lang1 小时前
数据链路层
linux·服务器·网络
z***3351 小时前
【MySQL系列文章】Linux环境下安装部署MySQL
linux·mysql·adb
执笔论英雄2 小时前
【RL】python协程
java·网络·人工智能·python·设计模式
zmzb01032 小时前
C++课后习题训练记录Day38
开发语言·c++
獭.獭.2 小时前
C++ -- STL【string的使用】
c++·string·auto
偶像你挑的噻2 小时前
13-Linux驱动开发-中断子系统
linux·驱动开发·stm32·嵌入式硬件
福尔摩斯张2 小时前
Linux进程间通信(IPC)机制深度解析与实践指南
linux·运维·服务器·数据结构·c++·算法
lijiatu100863 小时前
C++ 类成员变量声明语法错误
java·开发语言·c++
zore_c3 小时前
【C语言】带你层层深入指针——指针详解2
c语言·开发语言·c++·经验分享·笔记