JSON(JavaScript Object Notation)作为一种轻量级的数据交换格式,在跨平台通信、配置存储等场景中被广泛使用。而 CJSON 作为一款轻量级的 C 语言 JSON 库,凭借其简洁的 API 和极小的体积,成为 Qt 项目中处理 JSON 数据的理想选择。本文将从 CJSON 基础入手,逐步讲解其在 Qt 中的使用方法,包括数据组装、发送、解析及进阶的 JSON 流处理,并总结关键注意事项。
一、什么是 CJSON?
CJSON 是由 Dave Gamble 开发的一款开源 JSON 解析库,其核心优势在于:
- 轻量简洁:仅包含
cJSON.c
和cJSON.h
两个文件,无需依赖其他库,可直接嵌入项目。 - API 直观:提供了创建、解析、修改 JSON 数据的全套接口,上手成本低。
- 兼容性强:支持标准 JSON 语法(对象、数组、字符串、数字等类型),可在 C/C++ 项目中无缝使用。
在 Qt 项目中集成 CJSON 非常简单:只需将 cJSON.c
和 cJSON.h
复制到项目目录,并在 .pro
文件中添加源文件即可:
SOURCES += cJSON.c # 确保路径正确
二、Qt 中常用的 CJSON 函数及操作
CJSON 的核心操作围绕 “创建 JSON 数据” 和 “解析 JSON 数据” 展开,以下是 Qt 中最常用的函数及用法:
1. JSON 数据的创建与修改
函数 | 功能 | 示例 |
---|---|---|
cJSON_CreateObject() |
创建 JSON 对象({} ) |
cJSON* obj = cJSON_CreateObject(); |
cJSON_CreateArray() |
创建 JSON 数组([] ) |
cJSON* arr = cJSON_CreateArray(); |
cJSON_AddStringToObject() |
向对象添加字符串字段 | cJSON_AddStringToObject(obj, "name", "Qt"); |
cJSON_AddNumberToObject() |
向对象添加数字字段 | cJSON_AddNumberToObject(obj, "version", 6.5); |
cJSON_AddBoolToObject() |
向对象添加布尔字段 | cJSON_AddBoolToObject(obj, "enable", true); |
cJSON_AddItemToArray() |
向数组添加元素 | cJSON_AddItemToArray(arr, cJSON_CreateString("item1")); |
cJSON_PrintUnformatted() |
将 JSON 转为紧凑字符串(无格式) | char* str = cJSON_PrintUnformatted(obj); |
cJSON_Print() |
将 JSON 转为带格式字符串(有缩进) | char* str = cJSON_Print(obj); (适合调试) |
2. JSON 数据的解析与查询
函数 | 功能 | 示例 |
---|---|---|
cJSON_Parse() |
解析 JSON 字符串为 cJSON 结构体 | cJSON* root = cJSON_Parse(jsonStr); |
cJSON_GetObjectItem() |
从对象中获取字段 | cJSON* item = cJSON_GetObjectItem(root, "name"); |
cJSON_GetArraySize() |
获取数组长度 | int size = cJSON_GetArraySize(arr); |
cJSON_GetArrayItem() |
获取数组中指定索引的元素 | cJSON* elem = cJSON_GetArrayItem(arr, 0); |
cJSON_IsString() / cJSON_IsNumber() |
判断字段类型 | if (cJSON_IsString(item)) { ... } |
cJSON_GetErrorPtr() |
获取解析错误信息 | const char* err = cJSON_GetErrorPtr(); |
cJSON_Delete() |
释放 cJSON 结构体内存 | cJSON_Delete(root); |
3. 类型判断与值提取
解析 JSON 时,需先判断字段类型,再提取对应的值:
- 字符串:
item->valuestring
(需先通过cJSON_IsString(item)
验证) - 数字:
item->valuedouble
(浮点数)或item->valueint
(整数) - 布尔:
cJSON_IsTrue(item)
或cJSON_IsFalse(item)
判断
三、基础实战:简单 CJSON 数据的组装、发送与解析
以 “通过 UDP 发送一条包含设备信息的 JSON 数据,并在接收端解析” 为例,演示完整流程。
1. 组装并发送 JSON 数据
// 组装 JSON 数据
cJSON* deviceInfo = cJSON_CreateObject();
cJSON_AddStringToObject(deviceInfo, "id", "dev_001");
cJSON_AddNumberToObject(deviceInfo, "temp", 25.3);
cJSON_AddBoolToObject(deviceInfo, "online", true);
// 转为字符串(紧凑格式,适合传输)
char* jsonStr = cJSON_PrintUnformatted(deviceInfo);
if (!jsonStr) {
qDebug() << "JSON 转换失败";
return;
}
// 通过 UDP 发送(Qt 代码)
QUdpSocket* udpSocket = new QUdpSocket(this);
QByteArray sendData(jsonStr);
udpSocket->writeDatagram(
sendData,
QHostAddress("192.168.1.100"), // 目标 IP
8888 // 目标端口
);
// 释放资源
free(jsonStr); // cJSON 生成的字符串需用 free 释放
cJSON_Delete(deviceInfo); // 释放 cJSON 对象
2. 接收并解析 JSON 数据
// 接收 UDP 数据(Qt 代码)
void onUdpReceived() {
while (udpSocket->hasPendingDatagrams()) {
QByteArray recvData;
recvData.resize(udpSocket->pendingDatagramSize());
udpSocket->readDatagram(recvData.data(), recvData.size());
// 解析 JSON 数据
cJSON* root = cJSON_Parse(recvData.constData());
if (!root) {
qDebug() << "解析失败:" << cJSON_GetErrorPtr();
return;
}
// 提取字段值
cJSON* idItem = cJSON_GetObjectItem(root, "id");
if (cJSON_IsString(idItem)) {
qDebug() << "设备 ID:" << idItem->valuestring;
}
cJSON* tempItem = cJSON_GetObjectItem(root, "temp");
if (cJSON_IsNumber(tempItem)) {
qDebug() << "温度:" << tempItem->valuedouble;
}
// 释放内存
cJSON_Delete(root);
}
}
四、进阶实战:JSON 流的组装、发送与解析
当需要传输多条 JSON 数据(如批量设备信息)时,通常将其封装为 JSON 数组(即 “JSON 流”)。以下是具体实现:
1. 组装并发送 JSON 流(数组形式)
// 组装包含多条数据的 JSON 数组
cJSON* deviceArray = cJSON_CreateArray();
// 第一条数据
cJSON* dev1 = cJSON_CreateObject();
cJSON_AddStringToObject(dev1, "id", "dev_001");
cJSON_AddNumberToObject(dev1, "temp", 25.3);
cJSON_AddItemToArray(deviceArray, dev1);
// 第二条数据
cJSON* dev2 = cJSON_CreateObject();
cJSON_AddStringToObject(dev2, "id", "dev_002");
cJSON_AddNumberToObject(dev2, "temp", 26.1);
cJSON_AddItemToArray(deviceArray, dev2);
// 转为字符串并发送
char* jsonStr = cJSON_PrintUnformatted(deviceArray);
QByteArray sendData(jsonStr);
udpSocket->writeDatagram(sendData, QHostAddress("192.168.1.100"), 8888);
// 释放资源
free(jsonStr);
cJSON_Delete(deviceArray);
生成的 JSON 流格式:
json
[{"id":"dev_001","temp":25.3},{"id":"dev_002","temp":26.1}]
2. 解析 JSON 流(数组形式)
void parseJsonStream(const QByteArray& recvData) {
cJSON* rootArray = cJSON_Parse(recvData.constData());
if (!rootArray || !cJSON_IsArray(rootArray)) {
qDebug() << "解析数组失败:" << cJSON_GetErrorPtr();
return;
}
// 遍历数组中的每条数据
int arraySize = cJSON_GetArraySize(rootArray);
for (int i = 0; i < arraySize; i++) {
cJSON* item = cJSON_GetArrayItem(rootArray, i);
if (!cJSON_IsObject(item)) continue;
// 提取字段
const char* id = cJSON_GetObjectItem(item, "id")->valuestring;
double temp = cJSON_GetObjectItem(item, "temp")->valuedouble;
qDebug() << "第" << i+1 << "条设备:" << id << ",温度:" << temp;
}
cJSON_Delete(rootArray);
}
五、CJSON 使用注意事项
内存管理是重中之重
cJSON_Parse()
、cJSON_CreateObject()
等函数会动态分配内存,必须通过cJSON_Delete()
释放,否则会导致内存泄漏。cJSON_Print()
和cJSON_PrintUnformatted()
返回的字符串需用free()
释放(而非delete
),因为其内部使用malloc
分配内存。
类型检查不可少
解析时必须先用cJSON_IsString()
、cJSON_IsNumber()
等函数验证字段类型,避免因类型不匹配导致的空指针错误。例如:cJSON* item = cJSON_GetObjectItem(root, "filesize"); if (cJSON_IsNumber(item)) { // 先检查类型 int size = item->valueint; }
处理非标准 JSON 需谨慎
若遇到多条 JSON 拼接的 “非标准流”(如{"a":1}{"b":2}
),需按约定分隔符(如换行)拆分后逐个解析,避免cJSON_Parse()
解析失败。注意数据大小限制
UDP 传输时,JSON 数据需控制在 MTU 范围内(通常 1500 字节以内),超过时需拆分或改用 TCP。避免重复添加字段
向同一对象添加同名字段时,CJSON 会覆盖旧值,需确保字段名唯一。
六、总结
CJSON 以其轻量、高效的特点,成为 Qt 项目中处理 JSON 数据的优秀选择。通过本文的介绍,你可以掌握从基础的 JSON 组装、解析,到进阶的 JSON 流处理的全流程,同时注意内存管理、类型检查等关键细节,即可在实际项目中灵活运用。
无论是设备通信、数据存储还是配置解析,CJSON 都能帮助你简洁、高效地处理 JSON 数据,为跨平台数据交互提供可靠支持。