一、JSON 基础
在学习 JsonCpp 之前,我们先搞懂 JSON 的核心规范,这是后续的基础。
1.1 JSON 核心结构
JSON 仅包含两种核心结构,所有复杂数据均由二者嵌套组合而成:
- 对象:无序的键值对集合,使用大括号
{}包裹,是 JSON 的核心复合类型 - 数组:有序的值集合,使用方括号
[]包裹,用于存储同类型 / 多类型的有序数据 - 值:JSON 的最小单元,支持 6 种合法类型:字符串、数字、布尔值(true/false)、null、对象、数组
1.2 强制语法规则
JSON 的语法规范很严格,不符合规则的字符串会直接解析失败,规则如下:
- 所有键名必须是双引号包裹的 UTF-8 字符串,禁止单引号、无引号
- 键与值之间必须用英文冒号
:分隔,多个键值对 / 数组元素用英文逗号,分隔 - 禁止末尾逗号(trailing comma),即最后一个键值对 / 数组元素后不能加逗号
- 字符串必须使用双引号包裹,支持转义字符(如
\n、\t、\" - 数字支持整数、浮点数、科学计数法,无单双引号包裹
- 布尔值仅支持小写的
true/false,空值仅支持小写的null
1.3 标准 JSON 示例
这里给出一份符合规范的完整 JSON 示例,覆盖所有核心类型:
{
"name": "John Doe",
"age": 30,
"is_student": false,
"weight": 75.5,
"skills": [ "JavaScript", "Python", "C++" ],
"address": { "street": "123 Main St", "city": "Anytown", "state": "CA" },
"education": null
}
1.4 JSON 核心应用场景
- 数据交换:HTTP / 微服务 API 的标准数据传输格式,前后端、服务间通信的核心载体
- 配置文件:应用程序的配置管理,替代 ini、xml 等老旧格式,可读性与扩展性更强
- 数据存储:轻量级数据持久化,用于日志、用户配置、小型数据集的文件存储
- 数据序列化:跨语言、跨平台的数据序列化方案,兼容几乎所有主流编程语言
二、JsonCpp 库
JsonCpp 是一个专为 C++ 设计的高性能 JSON 处理库,完全兼容 JSON 标准规范,提供了 JSON 解析、生成、修改、遍历的全能力支持,是 C++ 生态中最成熟的 JSON 库之一。
2.1 核心特性优势
- 简单易用:提供直观的类 C++ 容器 API,学习成本极低,新手可快速上手
- 高性能:优化的解析与生成逻辑,可高效处理大批量 JSON 数据
- 全平台兼容:原生支持 Windows、Linux、macOS、嵌入式等几乎所有操作系统
- 无额外依赖:不依赖任何第三方库,可无缝集成到任何 C++ 项目中
- 标准兼容:完全支持 JSON 官方标准,同时提供注释、宽松解析等扩展能力
- 现代 C++ 支持:完美适配 C++11 及以上标准,支持智能指针、移动语义等现代特性
2.2 常见平台安装方法
2.2.1 Linux 系统(Debian/Ubuntu 系列)
官方 apt 源直接安装:
sudo apt update && sudo apt install libjsoncpp-dev
CentOS/RHEL 系列使用 yum 安装:
sudo yum install jsoncpp-devel
2.2.2 macOS 系统
通过 Homebrew 一键安装:
brew install jsoncpp
2.2.3 Windows 系统
推荐使用 vcpkg 包管理器安装,自动适配 Visual Studio 环境:
vcpkg install jsoncpp:x64-windows
2.2.4 源码编译安装(全平台通用)
适合需要自定义编译选项、适配特殊环境的场景:
- 克隆官方源码仓库:https://github.com/open-source-parsers/jsoncpp.git
- 进入源码目录,创建编译文件夹:
cd jsoncpp && mkdir build && cd build - CMake 生成编译配置:
cmake .. -DCMAKE_BUILD_TYPE=Release - 编译与安装:
make -j8 && sudo make install
2.3 开发环境基础配置
2.3.1 头文件引入
JsonCpp 的核心头文件有两种常见路径,根据安装方式适配:
- Debian/Ubuntu apt 安装:
#include <jsoncpp/json/json.h> - 源码编译、brew、vcpkg 安装:
#include <json/json.h>
2.3.2 编译链接
编译时必须链接 jsoncpp 动态库,GCC/Clang 编译参数需添加:
-ljsoncpp
Windows Visual Studio 需在项目属性中添加对应架构的 jsoncpp.lib 作为附加依赖项。
三、JsonCpp 核心 API
JsonCpp 的 API 设计极其清晰,核心分为三大模块:
Json::Value 核心数据类、StreamWriter 序列化模块、CharReader 反序列化模块
3.1 数据类型枚举 ValueType
JsonCpp 用枚举定义了 JSON 支持的所有数据类型,用于类型判断与校验,定义如下:
namespace Json {
enum ValueType {
nullValue = 0, // null 空值
intValue, // 有符号整型
uintValue, // 无符号整型
realValue, // 浮点型 (double)
stringValue, // UTF-8 字符串
booleanValue, // 布尔值
arrayValue, // 数组类型
objectValue // 对象类型 (键值对)
};
}
3.2 核心数据类 Json::Value
Json::Value是 JsonCpp 的核心,是一个变体类型,可存储任意 JSON 支持的数据类型,模拟了 JSON 的动态类型特性,提供了完整的类型判断、转换、读写能力。
3.2.1 类型判断接口
用于判断 Json::Value 存储的实际数据类型,是类型转换前的必操作,避免类型不匹配抛出异常:
| 接口方法 | 功能说明 |
|---|---|
bool isNull() const | 判断是否为 null 空值 |
bool isBool() const | 判断是否为布尔值 |
bool isInt() const | 判断是否为 32 位有符号整型 |
bool isInt64() const | 判断是否为 64 位有符号整型 |
bool isUInt() const | 判断是否为 32 位无符号整型 |
bool isUInt64() const | 判断是否为 64 位无符号整型 |
bool isDouble() const | 判断是否为浮点型 |
bool isNumeric() const | 判断是否为任意数字类型 (整型 / 浮点型) |
bool isString() const | 判断是否为字符串类型 |
bool isArray() const | 判断是否为数组类型 |
bool isObject() const | 判断是否为对象类型 |
3.2.2 类型转换接口
将 Json::Value 转换为对应的 C++ 原生类型,仅当类型匹配时可安全调用,否则会抛出 Json::Exception 异常:
| 接口方法 | 功能说明 |
|---|---|
const char* asCString() const | 转换为 C 风格 const char * 字符串,生命周期与原 Value 绑定 |
std::string asString() const | 转换为 std::string 字符串 |
Int asInt() const | 转换为 32 位有符号整型 int |
Int64 asInt64() const | 转换为 64 位有符号整型 long long |
UInt asUInt() const | 转换为 32 位无符号整型 unsigned int |
UInt64 asUInt64() const | 转换为 64 位无符号整型 unsigned long long |
float asFloat() const | 转换为单精度浮点型 float |
double asDouble() const | 转换为双精度浮点型 double |
bool asBool() const | 转换为布尔值 bool |
3.2.3 对象 (键值对) 操作接口
用于操作 JSON 对象的键值对,实现键的增删改查:
| 接口方法 | 功能说明 |
|---|---|
Value& operator[](const char* key) | 通过键名访问对应值,键不存在时自动插入 null 值 |
Value& operator[](const std::string& key) | 同上,支持 std::string 类型键名 |
bool isMember(const std::string& key) const | 判断对象中是否存在指定键名,无副作用 |
void removeMember(const std::string& key) | 删除对象中指定的键值对 |
std::vector<std::string> getMemberNames() const | 获取对象中所有键名的列表 |
3.2.4 数组操作接口
用于操作 JSON 数组,实现元素的增删改查与遍历:
| 接口方法 | 功能说明 |
|---|---|
ArrayIndex size() const | 获取数组元素个数,ArrayIndex 为 unsigned int 别名 |
bool empty() const | 判断数组是否为空 |
void clear() | 清空数组所有元素 |
void resize(ArrayIndex newSize) | 重置数组大小,扩容部分填充 null 值 |
Value& operator[](ArrayIndex index) | 通过下标访问数组元素,下标越界时自动扩容 |
Value& append(const Value& value) | 向数组末尾追加元素 |
3.2.5 辅助接口
| 接口方法 | 功能说明 |
|---|---|
void swap(Value& other) | 与另一个 Value 对象交换数据,无内存拷贝,高性能 |
void copy(const Value& other) | 深拷贝另一个 Value 对象的所有数据 |
std::string toStyledString() const | 快速序列化为带格式化的 JSON 字符串,调试专用 |
3.3 序列化核心:StreamWriter 系列 API
序列化指的是将 C++ 中的
Json::Value对象转换为 JSON 格式字符串核心类为
Json::StreamWriterBuilder(工厂类)与Json::StreamWriter(序列化执行类)。
核心 API 定义:
namespace Json {
// 序列化执行类
class StreamWriter {
public:
// 核心方法:将 root 对象序列化,写入输出流 sout
virtual int write(Value const& root, std::ostream* sout) = 0;
};
// 序列化工厂类,用于配置序列化规则、创建 StreamWriter 对象
class StreamWriterBuilder : public StreamWriter::Factory {
public:
// 序列化配置项,可自定义格式化、注释、编码等规则
Json::Value settings_;
// 创建 StreamWriter 对象,返回值需用智能指针管理
StreamWriter* newStreamWriter() const override;
};
// 工具函数:直接将 Value 对象序列化为 std::string
std::string writeString(StreamWriter::Factory const& factory, Value const& root);
}
常用序列化配置项:
| 配置项 | 取值 | 功能说明 |
|---|---|---|
commentStyle | "None"/"All" | 注释输出控制,None 为不输出注释,All 为输出所有注释 |
indentation | 字符串 | 缩进字符,默认 " "(两个空格),设为 "" 则生成无缩进的压缩 JSON |
emitUTF8 | true/false | 是否强制输出 UTF-8 编码,默认 true,建议保持开启 |
precision | 数字 | 浮点数序列化的精度,默认 15 位有效数字 |
3.4 反序列化核心:CharReader 系列 API
反序列化指的是将 JSON 格式字符串解析为 C++ 中的
Json::Value对象核心类为
Json::CharReaderBuilder(工厂类)与Json::CharReader(反序列化执行类)。
核心 API 定义:
namespace Json {
// 反序列化执行类
class CharReader {
public:
// 核心方法:解析 [beginDoc, endDoc) 区间的 JSON 字符串
// 解析成功返回 true,结果存入 root;失败返回 false,错误信息存入 errs
virtual bool parse(
char const* beginDoc, char const* endDoc,
Value* root, std::string* errs
) = 0;
};
// 反序列化工厂类,用于配置解析规则、创建 CharReader 对象
class CharReaderBuilder : public CharReader::Factory {
public:
// 反序列化配置项,可自定义注释支持、宽松解析等规则
Json::Value settings_;
// 创建 CharReader 对象,返回值需用智能指针管理
CharReader* newCharReader() const override;
};
}
常用反序列化配置项(settings_):
| 配置项 | 取值 | 功能说明 |
|---|---|---|
allowComments | true/false | 是否支持 JSON 中的 // 单行注释与 /* */ 块注释,默认 false |
strictRoot | true/false | 是否严格要求根节点必须是对象或数组,默认 false |
allowTrailingCommas | true/false | 是否允许末尾逗号,默认 false |
failIfExtra | true/false | 解析到多余非空白字符时是否失败,默认 true |
四、基本使用方法和示例
(一)基础类型操作
1. 赋值与取值
#include <iostream>
#include <jsoncpp/json/json.h>
int main() {
// 1. 字符串类型
Json::Value str_val;
str_val = "Hello JsonCpp"; // 赋值
if (str_val.isString()) // 类型判断
{
std::cout << "字符串值:" << str_val.asString() << std::endl;
}
// 2. 整数类型
Json::Value int_val;
int_val = 100;
if (int_val.isInt())
{
std::cout << "整数值:" << int_val.asInt() << std::endl;
}
// 3. 浮点类型
Json::Value double_val;
double_val = 3.14159;
if (double_val.isDouble())
{
std::cout << "浮点值:" << double_val.asDouble() << std::endl;
}
// 4. 布尔类型
Json::Value bool_val;
bool_val = true;
if (bool_val.isBool())
{
std::cout << "布尔值:" << std::boolalpha << bool_val.asBool() << std::endl;
}
// 5. null 类型
Json::Value null_val;
null_val = Json::nullValue; // 显式赋值 null
if (null_val.isNull())
{
std::cout << "null 值:null" << std::endl;
}
return 0;
}
输出结果:
字符串值:Hello JsonCpp
整数值:100
浮点值:3.14159
布尔值:true
null 值:null
2. 关键方法说明
| 方法 | 作用 |
|---|---|
isString()/asString() | 判断 / 转换为字符串类型 |
isInt()/asInt() | 判断 / 转换为 32 位整数(也支持 isInt64/asInt64) |
isDouble()/asDouble() | 判断 / 转换为浮点类型 |
isBool()/asBool() | 判断 / 转换为布尔类型 |
isNull() | 判断是否为 null 值(无 asNull () 方法) |
(二)JSON 对象(键值对)操作
JSON 对象是无序键值对集合,核心操作是「增 / 删 / 改 / 查」键值对。
1. 完整示例
#include <iostream>
#include <vector>
#include <jsoncpp/json/json.h>
int main() {
// 1. 创建空 JSON 对象
Json::Value obj;
// 2. 新增/修改键值对(key 不存在则新增,存在则覆盖)
obj["name"] = "张三";
obj["age"] = 25;
obj["is_student"] = false;
obj["score"] = 95.5;
// 3. 检查键是否存在(避免 operator[] 的副作用)
if (obj.isMember("name")) // 关键:先判断键存在,再取值
{
std::cout << "姓名:" << obj["name"].asString() << std::endl;
}
// 4. 获取所有键名(遍历对象)
std::vector<std::string> keys = obj.getMemberNames();
std::cout << "\n对象所有键值对:" << std::endl;
for (const auto& key : keys)
{
std::cout << key << " = " << obj[key] << std::endl;
}
// 5. 删除键值对
obj.removeMember("score");
std::cout << "\n删除 score 后,是否还存在:" << std::boolalpha << obj.isMember("score") << std::endl;
// 6. 安全取值(带默认值,无副作用)
std::string city = obj.get("city", "北京").asString();
std::cout << "城市(默认值):" << city << std::endl;
;
}
输出结果:
姓名:张三
对象所有键值对:
name = "张三"
age = 25
is_student = false
score = 95.5
删除 score 后,是否还存在:false
城市(默认值):北京
2. 核心方法说明
| 方法 | 作用 |
|---|---|
operator[](const string& key) | 通过键访问值(无键则自动插入 null) |
isMember(const string& key) | 判断键是否存在(无副作用,必用) |
getMemberNames() | 获取所有键名的 vector 列表 |
removeMember(const string& key) | 删除指定键值对 |
get(key, defaultValue) | 安全取值(无键则返回默认值,无副作用) |
(三)JSON 数组操作
JSON 数组是有序值集合,核心操作是「追加 / 遍历 / 修改 / 清空」。
1. 完整示例
#include <iostream>
#include <jsoncpp/json/json.h>
int main() {
// 1. 创建空数组
Json::Value arr;
// 2. 追加元素(支持不同类型)
arr.append("C++");
arr.append("Python");
arr.append(100);
arr.append(true);
// 3. 获取数组长度
std::cout << "数组长度:" << arr.size() << std::endl;
// 4. 遍历数组(下标方式)
std::cout << "\n数组元素:" << std::endl;
for (int i = 0; i < arr.size(); ++i)
{
std::cout << "下标" << i << ":" << arr[i] << std::endl;
}
// 5. 修改数组元素
arr[1] = "Java"; // 覆盖下标 1 的元素
std::cout << "\n修改后下标 1:" << arr[1].asString() << std::endl;
// 6. 清空数组
arr.clear();
std::cout << "清空后数组长度:" << arr.size() << std::endl;
// 7. 重置数组大小(扩容/缩容)
arr.resize(3); // 扩容到 3 个元素,默认填充 null
std::cout << "扩容后下标 2:" << (arr[2].isNull() ? "null" : "非 null") << std::endl;
return ;
}
输出结果:
数组长度:4
数组元素:
下标 0:"C++"
下标 1:"Python"
下标 2:100
下标 3:true
修改后下标 1:Java
清空后数组长度:0
扩容后下标 2:null
2. 核心方法说明
| 方法 | 作用 |
|---|---|
append(const Value&) | 向数组末尾追加元素 |
size() | 获取数组元素个数(返回 ArrayIndex 类型,可转 int) |
operator[](int index) | 通过下标访问元素(越界则自动扩容) |
clear() | 清空数组所有元素 |
resize(int newSize) | 重置数组大小(扩容填充 null,缩容截断) |
empty() | 判断数组是否为空(size ()==0) |
(四)嵌套结构(对象嵌套对象 / 数组)
实际开发中最常用的场景,核心是「逐层创建 + 逐层访问」。
1. 完整示例
#include <iostream>
#include <jsoncpp/json/json.h>
int main() {
// 1. 创建嵌套对象(地址)
Json::Value addr;
addr["province"] = "广东省";
addr["city"] = "深圳市";
addr["street"] = "科技园路";
// 2. 创建嵌套数组(爱好)
Json::Value hobbies;
hobbies.append("编程");
hobbies.append("阅读");
hobbies.append("跑步");
// 3. 根对象嵌套子对象和数组
Json::Value root;
root["user"] = "李四";
root["age"] = 30;
root["address"] = addr; // 嵌套对象
root["hobbies"] = hobbies; // 嵌套数组
// 4. 访问嵌套对象
if (root.isMember("address") && root["address"].isObject())
{
std::cout << "省份:" << root["address"]["province"].asString() << std::endl;
}
// 5. 访问嵌套数组
if (root.isMember("hobbies") && root["hobbies"].isArray())
{
std::cout << "\n爱好列表:" << std::endl;
for (int i = ; i < root[].(); ++i)
{
std::cout << << root[][i].() << std::endl;
}
}
std::cout << << std::endl;
std::cout << root.() << std::endl;
;
}
输出结果:
省份:广东省
爱好列表:
- 编程
- 阅读
- 跑步
完整 JSON 结构:
{
"address" : {
"city" : "深圳市",
"province" : "广东省",
"street" : "科技园路"
},
"age" : 30,
"hobbies" : [
"编程",
"阅读",
"跑步"
],
"user" : "李四"
}
(五)序列化(Value → JSON 字符串)
步骤构建
Json::Value数据结构;配置Json::StreamWriterBuilder(可选,默认紧凑输出);生成 JSON 字符串。
#include <json/json.h>
#include <iostream>
#include <sstream>
#include <string>
int main() {
// 1. 构建 Json::Value
Json::Value root;
root["name"] = "张三";
root["age"] = 25;
root["is_student"] = false;
// 数组
Json::Value hobbies;
hobbies.append("篮球");
hobbies.append("编程");
root["hobbies"] = hobbies;
// 嵌套对象
Json::Value address;
address["city"] = "北京";
root["address"] = address;
// 2. 配置序列化(格式化输出,4 空格缩进)
Json::StreamWriterBuilder writerBuilder; // 创建 JSON 序列化配置构建器
writerBuilder["indentation"] = " "; // Json::StreamWriter:实际执行序列化写入的抽象基类,只能通过 Builder 的 newStreamWriter() 方法创建,禁止直接 new 实例
// std::unique_ptr:C++ 智能指针,自动管理写入器实例的生命周期
// newStreamWriter():const 方法,会基于当前 Builder 的配置生成一个独立的写入器实例;同一个 Builder 可重复创建多个 Writer,线程安全
std::unique_ptr<Json::StreamWriter> writer(writerBuilder.newStreamWriter());
// 3. 生成 JSON 字符串
// 核心作用:将 Json::Value 数据写入输出流,最终转换为标准 std::string 字符串
// 3.1 创建字符串输出流,作为序列化的输出载体
// std::ostringstream 是 C++ 标准库的字符串输出流,专门用于内存中的字符串拼接
std::ostringstream oss;
writer->(root, &oss);
std::string jsonStr = oss.();
std::cout << << jsonStr << std::endl;
;
}
除了
indentation,你还可以通过writerBuilder添加这些常用配置:
列举常见的序列化方式:
方式 1:便捷函数 writeString(推荐日常使用)
核心特点一行代码完成序列化,是
StreamWriter的高级封装 可配置缩进(紧凑 / 格式化输出) 直接返回std::string,无需手动操作流适用场景绝大多数日常场景(接口请求、数据存储) 需要灵活配置缩进格式的场景 单次 / 少量序列化操作(无需复用写入器)
方式 2:手动创建 StreamWriter(灵活输出控制)
核心特点底层用法,手动管理
StreamWriter生命周期 可复用写入器(多次序列化更高效) 支持输出到任意std::ostream(控制台、文件、网络流)适用场景多次序列化(循环中复用写入器,减少创建开销) 需要输出到文件 / 网络流等非字符串目标 需精细控制输出过程的场景
方式 3:toStyledString(极简调试用)
核心特点零配置,一行代码完成固定格式化输出(带缩进 / 换行,不可修改) 底层封装了默认的
StreamWriter适用场景开发调试、日志打印 临时查看 JSON 结构(无需配置格式) 不推荐生产环境(固定格式,无法紧凑输出)
方式 4:自定义 StreamWriter(高级定制)
核心特点继承
Json::StreamWriter重写序列化逻辑 支持特殊规则(如自定义布尔值、日期格式) 极致灵活,但开发成本高适用场景需要自定义序列化规则(如特殊字符转义、非标格式) 对接特殊第三方接口(要求非标准 JSON 格式) 非必要不使用(增加维护成本)
(六)反序列化(JSON 字符串 → Value)
步骤配置
Json::CharReaderBuilder(可选,默认宽松解析);调用parse()解析字符串到Json::Value;安全取值(先判断类型,再转换)。
#include <json/json.h>
#include <iostream>
#include <string>
int main() {
// 待解析的 JSON 字符串
std::string jsonStr = R"({ "name": "张三", "age": 25, "hobbies": ["篮球", "编程"], "address": {"city": "北京"} })";
// 1. 配置反序列化
Json::CharReaderBuilder readerBuilder; // 创建 JSON 反序列化配置构建器
readerBuilder["rejectDupKeys"] = true; // 拒绝重复键
// 根据配置创建 JSON 解析器实例
// Json::CharReader:实际执行 JSON 字符串解析的抽象基类,只能通过 Builder 的 newCharReader() 方法创建,不可直接 new
// std::unique_ptr:智能指针,自动管理解析器实例的生命周期
// newCharReader():const 方法,会基于当前 Builder 的配置生成一个独立的解析器实例;同一个 Builder 可重复创建多个 Reader,线程安全
std::unique_ptr<Json::CharReader> reader(readerBuilder.newCharReader());
// 2. 解析
Json::Value root; // 接收解析结果的容器
std::string errs; // 接收解析错误信息的容器
bool parseOk = reader->parse(
jsonStr.c_str(), // 待解析 JSON 字符串的起始内存地址
jsonStr.c_str() + jsonStr.size(), // 待解析 JSON 字符串的结束内存地址
&root, &errs
);
if (!parseOk)
{
std::cerr << "解析失败:" << errs << std::endl;
return 1;
}
// 3. 安全取值
std::cout << "姓名:" << root.get("name", "未知").() << std::endl;
std::cout << << root.(, ).() << std::endl;
(root[].())
{
std::cout << ;
( & hobby : root[])
{
std::cout << hobby.() << ;
}
std::cout << std::endl;
}
(root[].())
{
std::cout << << root[][].() << std::endl;
}
;
}
解析步骤说明:
几种常见的反序列化的解析方式:
1. parseFromStream(主流)
核心特点:基于
std::istream设计,支持字符串流、文件流、网络流等任意输入源 可通过CharReaderBuilder配置解析规则(如注释支持、精度控制) 线程安全、扩展性强适用场景:新项目首选(兼容所有 JsonCpp 新版本) 处理大文件 / 流式数据(无需一次性加载全部内容) 需要自定义解析规则的场景
2. fromString(简单)
核心特点:
parseFromStream的封装版,直接接收字符串,无需手动创建流 零配置快速解析,牺牲部分灵活性换取简洁性适用场景:简单场景(无需配置解析规则) 快速调试、小体积 JSON 字符串解析
3. Json::Reader::parse(旧版 API)
核心特点:早期 JsonCpp 主流方式,现已被标记为「过时」 直接操作字符串指针,C 风格明显 配置选项少,非线程安全
适用场景:维护老旧项目(无法升级 JsonCpp 版本) 临时兼容历史代码(建议逐步替换为新 API)
4. 自定义 CharReader(高级解析)
核心特点:继承
Json::CharReader重写解析逻辑 支持非标 JSON 格式(如自定义注释、特殊字符) 开发成本高,仅适用于极特殊场景建议:新项目优先使用
parseFromStream(兼顾灵活性和性能);简单场景可使用FromString简化代码;
(七)JSON 文件的读写操作
实际开发中,我们经常需要从 JSON 文件读取配置,或将 JSON 数据写入文件持久化,这里给出完整的文件读写实现。
#include <iostream>
#include <fstream>
#include <memory>
#include <jsoncpp/json/json.h>
// 从文件读取并解析 JSON
bool readJsonFromFile(const std::string& file_path, Json::Value& root) {
// 打开文件
std::ifstream ifs(file_path, std::ios::in | std::ios::binary);
if (!ifs.is_open())
{
std::cerr << "文件打开失败:" << file_path << std::endl;
return false;
}
// 读取文件内容到字符串
std::string json_str((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>());
ifs.close();
// 反序列化解析
Json::CharReaderBuilder crb;
crb.settings_["allowComments"] = true;
std::unique_ptr<Json::CharReader> pcr(crb.newCharReader());
std::string err_msg;
bool ret = pcr->parse(json_str.data(), json_str.data() + json_str.size(), &root, &err_msg);
if (!ret)
{
std::cerr << "JSON 解析失败:" << err_msg << std::endl;
return false;
}
std::cout << "JSON 文件读取解析成功" << std::endl;
;
}
{
Json::StreamWriterBuilder swb;
swb.settings_[] = ;
swb.settings_[] = ;
;
;
(!ofs.())
{
std::cerr << << file_path << std::endl;
;
}
ret = psw->(root, &ofs);
ofs.();
(ret != )
{
std::cerr << << std::endl;
;
}
std::cout << << std::endl;
;
}
{
Json::Value config;
(!(, config))
{
;
}
config[] = ;
config[] = ;
config[][] = ;
(!(, config))
{
;
}
;
}
五、避坑指南
6.1 坑点与解决方案
坑 1:operator [] 的副作用(最常见)
问题:使用非 const 的
Json::Value的operator[]访问不存在的键时,会自动向对象中插入该键,值为 null,导致原对象被意外修改。
// 错误示例:意外插入了"not_exist_key"键
Json::Value obj;
if (obj["not_exist_key"].isNull())
{
std::cout << "键不存在" << std::endl;
}
// 此时 obj 中已经存在"not_exist_key"键,值为 null
解决方案:
- 先通过
isMember()判断键是否存在,再访问值 - 使用 const 引用访问,const 版本的
operator[]不会修改原对象,键不存在时返回静态 null 值 - 使用
get(const char* key, const Value& defaultValue)方法,无副作用,支持默认值
坑 2:类型不匹配导致的程序崩溃
问题:未判断类型直接调用
asXXX()方法,类型不匹配时会直接抛出Json::Exception异常,未捕获时会导致程序崩溃。解决方案:严格遵循「先判断类型,再转换取值」的流程,所有类型转换前必须做类型校验。
坑 3:数组越界自动扩容
问题:使用
operator[]访问数组下标超过数组长度时,会自动将数组扩容到该下标 + 1 的大小,扩容部分填充 null 值,导致数组意外变大。解决方案:访问数组前先判断下标是否小于数组
size(),禁止越界访问。
坑 4:带注释的 JSON 解析失败
问题:JSON 字符串中包含
//或/* */注释时,默认解析会直接失败。解决方案:显式开启注释支持:
crb.settings_["allowComments"] = true;
坑 5:字符串编码乱码
问题:传入 GBK、GB2312 等非 UTF-8 编码的字符串,序列化 / 反序列化后出现乱码。
解决方案:JsonCpp 原生仅支持 UTF-8 编码,所有字符串必须转换为 UTF-8 格式后再传入。
坑 6:线程安全问题
问题:多线程同时读写同一个
Json::Value对象,出现数据竞争、程序崩溃。解决方案:
Json::Value不是线程安全的,多线程环境下,读写同一个对象必须加互斥锁保护。
6.2 编码建议
- const 优先:访问 JSON 数据时,优先使用 const 引用,避免
operator[]的副作用,同时提升性能 - 异常捕获:所有 JsonCpp 操作都建议包裹在 try-catch 中,捕获
Json::Exception异常,避免程序意外崩溃 - 结构化封装:复杂 JSON 结构封装为 C++ 结构体,提供对应的序列化 / 反序列化方法,提升代码可维护性
- 内存优化:大数据量 JSON 处理时,使用
swap()方法避免深拷贝,使用resize()预分配数组空间 - 网络传输优化:序列化网络传输的 JSON 时,关闭缩进(
indentation="")生成压缩格式,减少数据体积 - 调试优化:调试时使用
toStyledString()快速生成格式化 JSON,定位数据问题
六、JsonCpp 与其他 C++ JSON 库对比
C++ 生态中还有多个优秀的 JSON 库,这里做横向对比
| 库名称 | 核心特点 | 优势 | 劣势 | 适用场景 |
|---|---|---|---|---|
| JsonCpp | 经典全功能库,非 header-only | 兼容性极强,支持老 C++ 标准,稳定成熟,工业级验证 | 性能中等,现代特性支持不如新库 | 老项目维护、需要兼容 C++03、企业级稳定优先的项目 |
| nlohmann/json | 现代 C++ header-only 库 | 用法极简,STL 兼容,功能丰富,header-only 无需编译 | 编译耗时较长,大数据量性能一般 | 新项目快速开发、追求开发效率、无需极致性能的场景 |
| RapidJSON | 腾讯开源的极致性能库 | 性能顶尖,内存占用极低,全功能支持 | API 较复杂,学习成本高,报错不友好 | 高频大数据量 JSON 处理、性能敏感的场景(如游戏、高并发服务) |


