JSON
前言
JSON是轻量级数据交换格式,兼具人类可读性和机器解析效率,广泛用于API数据传输、配置文件等场景。cJSON是C语言环境下的轻量JSON解析库,提供完整的JSON构建、解析、修改接口,本文整合JSON基础、cJSON核心用法及实战示例,补充拓展知识,方便日常开发查阅。
JSON基础
核心结构
JSON有两种基础结构,可组合嵌套形成复杂数据:
- 对象:用
{}包裹,由键值对组成。键名必须用双引号包裹,键值间用:分隔,键值对间用,分隔。 - 数组:用
[]包裹,是有序的值列表,值间用,分隔。
支持的数据类型
- 字符串:用双引号包裹(例:
"name": "Alice") - 数字:整数或浮点数(例:
123、12.34) - 布尔值:
true或false - 空值:
null - 对象:嵌套的
{}结构 - 数组:嵌套的
[]结构
完整示例
{
"name": "John Doe",
"age": 30,
"isEmployed": true,
"address": {
"street": "123 Main St",
"city": "New York"
},
"hobbies": ["reading", "hiking", "coding"]
}
核心特点
- 轻量级:比XML更简洁,数据传输效率更高
- 易读性:结构清晰,人类可直接阅读编辑
- 跨语言支持:几乎所有编程语言都有解析/生成库
- 用途广泛:Web API、配置文件、数据存储等场景通用
语法注意事项
- 键名必须用双引号(单引号无效)
- 不能包含注释(// 或 /* */ 均不支持)
- 末尾不能有多余逗号(例:
{"a":1, "b":2,}无效) - 字符串中特殊字符需转义(例:
"desc": "He said \"Hello\"")
cJSON库常用接口
cJSON库提供JSON解析、构建、修改、查询的完整接口,按功能分类整理如下(含使用关键说明):
解析JSON(字符串→cJSON对象)
| 函数 | 说明 |
|---|---|
cJSON *cJSON_Parse(const char *value) |
解析JSON字符串,返回cJSON对象。失败返回NULL,需后续检查 |
cJSON *cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated) |
高级解析,可返回解析结束位置,支持非空终止字符串 |
const char *cJSON_GetErrorPtr(void) |
解析失败后调用,获取错误位置指针(定位语法错误) |
生成JSON(cJSON对象→字符串)
| 函数 | 说明 |
|---|---|
char *cJSON_Print(const cJSON *item) |
生成格式化JSON字符串(带缩进换行),需手动free释放 |
char *cJSON_PrintUnformatted(const cJSON *item) |
生成紧凑JSON字符串(无空格换行),节省存储空间 |
char *cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt) |
缓冲式生成,减少内存分配次数,适合大数据量场景 |
创建/修改JSON对象
| 函数 | 说明 |
|---|---|
cJSON *cJSON_CreateObject(void) |
创建空JSON对象({}) |
cJSON *cJSON_CreateArray(void) |
创建空JSON数组([]) |
cJSON *cJSON_CreateString(const char *string) |
创建字符串类型节点 |
cJSON *cJSON_CreateNumber(double num) |
创建数字类型节点(整数/浮点数通用) |
cJSON *cJSON_CreateBool(cJSON_bool boolean) |
创建布尔类型节点(true/false) |
cJSON *cJSON_CreateNull(void) |
创建null类型节点 |
void cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item) |
向对象添加键值对(object必须是对象类型) |
void cJSON_AddItemToArray(cJSON *array, cJSON *item) |
向数组添加元素(array必须是数组类型) |
void cJSON_Delete(cJSON *item) |
递归释放cJSON对象及所有子节点内存(避免内存泄漏) |
查询JSON数据
| 函数 | 说明 |
|---|---|
cJSON *cJSON_GetObjectItem(const cJSON *object, const char *string) |
按键名获取对象值,不区分大小写,不存在返回NULL |
cJSON *cJSON_GetObjectItemCaseSensitive(const cJSON *object, const char *string) |
区分大小写的键名查询 |
cJSON *cJSON_GetArrayItem(const cJSON *array, int index) |
按索引获取数组元素,越界返回NULL |
int cJSON_GetArraySize(const cJSON *array) |
返回数组长度(元素个数) |
数据类型判断
| 函数 | 说明 |
|---|---|
cJSON_bool cJSON_IsInvalid(const cJSON *item) |
判断是否为无效节点(未初始化) |
cJSON_bool cJSON_IsFalse(const cJSON *item) |
判断是否为false |
cJSON_bool cJSON_IsTrue(const cJSON *item) |
判断是否为true |
cJSON_bool cJSON_IsBool(const cJSON *item) |
判断是否为布尔类型(true/false) |
cJSON_bool cJSON_IsNull(const cJSON *item) |
判断是否为null类型 |
cJSON_bool cJSON_IsNumber(const cJSON *item) |
判断是否为数字类型 |
cJSON_bool cJSON_IsString(const cJSON *item) |
判断是否为字符串类型 |
cJSON_bool cJSON_IsArray(const cJSON *item) |
判断是否为数组类型 |
cJSON_bool cJSON_IsObject(const cJSON *item) |
判断是否为对象类型 |
数据提取
| 函数 | 说明 |
|---|---|
char *cJSON_GetStringValue(const cJSON *item) |
提取字符串值(节点必须是字符串类型) |
double cJSON_GetNumberValue(const cJSON *item) |
提取数字值(节点必须是数字类型) |
int cJSON_GetValueInt(const cJSON *item) |
提取整数值(自动将浮点数转为整数) |
修改数据与工具函数
| 函数 | 说明 |
|---|---|
void cJSON_SetValuestring(cJSON *object, const char *valuestring) |
修改字符串节点的值(需确保节点为字符串类型) |
cJSON *cJSON_Duplicate(const cJSON *item, cJSON_bool recurse) |
深拷贝cJSON对象,recurse=1递归拷贝子节点 |
void cJSON_Minify(char *json) |
压缩JSON字符串(去除空格、换行、制表符) |
cJSON使用准备工作
获取cJSON源码
下载源码包:v1.7.15.tar.gz(稳定版本,兼容性好)
解压源码包
在Linux环境下执行解压命令:
tar -zxvf v1.7.15.tar.gz
编译为库文件(可选)
- 默认编译(x86平台):直接执行
make,生成libcjson.so(动态库)和libcjson.a(静态库) - 交叉编译(ARM平台):修改Makefile第27行的编译器:
修改后执行# 原配置(x86) CC = gcc -std=c89 # 修改后(ARM) CC = arm-linux-gcc -std=c89make,生成ARM平台可用库文件
直接使用源码文件
无需编译库,直接将解压后的json.c和json.h复制到项目目录,在代码中包含头文件即可使用:
#include "cJSON.h"
cJSON实战示例
解析JSON数据
目标:解析包含嵌套对象和数组的JSON字符串,提取所有字段值
示例代码
#include <stdio.h>
#include "cJSON.h"
// 待解析的JSON字符串
char *JsonMsg = "{\
\"name\": \"John Doe\",\
\"age\": 30,\
\"isEmployed\": true,\
\"address\": {\
\"street\": \"123 Main St\",\
\"city\": \"New York\"\
},\
\"hobbies\": [\"reading\", \"hiking\", \"coding\"]\
}";
int main(int argc, char const *argv[]) {
printf("原始JSON字符串:\n%s\n", JsonMsg);
// 1. 解析JSON字符串为cJSON对象(格式验证+转换)
cJSON *root = cJSON_Parse(JsonMsg);
if (root == NULL) {
// 解析失败,打印错误位置
printf("JSON解析失败: %s\n", cJSON_GetErrorPtr());
return 1;
}
printf("\nJSON对象解析成功!\n");
// 2. 提取基础类型字段
cJSON *name = cJSON_GetObjectItem(root, "name");
printf("姓名: %s\n", name->valuestring); // 字符串类型取值
cJSON *age = cJSON_GetObjectItem(root, "age");
printf("年龄: %d\n", age->valueint); // 整数类型取值
cJSON *isEmployed = cJSON_GetObjectItem(root, "isEmployed");
printf("是否就业: %s\n", isEmployed->valueint ? "是" : "否"); // 布尔类型取值
// 3. 提取嵌套对象字段
cJSON *address = cJSON_GetObjectItem(root, "address");
cJSON *street = cJSON_GetObjectItem(address, "street");
cJSON *city = cJSON_GetObjectItem(address, "city");
printf("地址: %s, %s\n", street->valuestring, city->valuestring);
// 4. 提取数组字段
cJSON *hobbies = cJSON_GetObjectItem(root, "hobbies");
int hobbyCount = cJSON_GetArraySize(hobbies);
printf("爱好(共%d个): ", hobbyCount);
for (int i = 0; i < hobbyCount; i++) {
cJSON *hobby = cJSON_GetArrayItem(hobbies, i);
printf("%s ", hobby->valuestring);
}
printf("\n");
// 5. 释放内存(递归释放所有子节点)
cJSON_Delete(root);
return 0;
}
输出结果
原始JSON字符串:
{ "name": "John Doe", "age": 30, "isEmployed": true, "address": { "street": "123 Main St", "city": "New York" }, "hobbies": ["reading", "hiking", "coding"]}
JSON对象解析成功!
姓名: John Doe
年龄: 30
是否就业: 是
地址: 123 Main St, New York
爱好(共3个): reading hiking coding
封装JSON数据
目标:构建包含嵌套对象和数组的JSON对象,生成字符串
示例代码
#include <stdio.h>
#include <stdbool.h>
#include "cJSON.h"
int main(int argc, char const *argv[]) {
// 1. 创建根对象(最外层JSON对象)
cJSON *rootObj = cJSON_CreateObject();
// 2. 向根对象添加基础类型字段
cJSON_AddStringToObject(rootObj, "Name", "Even"); // 字符串
cJSON_AddNumberToObject(rootObj, "Age", 30); // 数字
cJSON_AddBoolToObject(rootObj, "Teacher", true); // 布尔值
// 3. 创建嵌套对象(地址信息)
cJSON *addressObj = cJSON_CreateObject();
cJSON_AddStringToObject(addressObj, "Street", "成华大道");
cJSON_AddStringToObject(addressObj, "City", "重庆");
// 将嵌套对象添加到根对象
cJSON_AddItemToObject(rootObj, "Address", addressObj);
// 4. 创建数组(爱好列表)
cJSON *hobbiesArr = cJSON_CreateArray();
cJSON_AddItemToArray(hobbiesArr, cJSON_CreateString("唱歌"));
cJSON_AddItemToArray(hobbiesArr, cJSON_CreateString("跳舞"));
cJSON_AddItemToArray(hobbiesArr, cJSON_CreateString("RAP"));
cJSON_AddItemToArray(hobbiesArr, cJSON_CreateString("篮球"));
// 将数组添加到根对象
cJSON_AddItemToObject(rootObj, "Hobbies", hobbiesArr);
// 5. 生成格式化JSON字符串(便于阅读)
char *jsonStr = cJSON_Print(rootObj);
printf("生成的JSON字符串:\n%s\n", jsonStr);
// 6. 释放资源(先释放字符串,再释放cJSON对象)
free(jsonStr);
cJSON_Delete(rootObj);
return 0;
}
输出结果
生成的JSON字符串:
{
"Name": "Even",
"Age": 30,
"Teacher": true,
"Address": {
"Street": "成华大道",
"City": "重庆"
},
"Hobbies": ["唱歌", "跳舞", "RAP", "篮球"]
}
拓展
JSON与XML对比(数据交换格式选择)
| 特性 | JSON | XML |
|---|---|---|
| 语法简洁性 | 非常简洁,键值对结构 | 繁琐,标签嵌套多 |
| 解析效率 | 高,轻量级解析库即可 | 较低,需XML解析器(如libxml2) |
| 数据体积 | 小,无冗余标签 | 大,标签占用额外空间 |
| 可读性 | 人类易读,结构清晰 | 可读性一般,标签干扰信息 |
| 跨语言支持 | 全语言支持 | 全语言支持,但接口更复杂 |
| 适用场景 | Web API、配置文件、移动端数据传输 | 企业级系统、文档标记、SOAP协议 |
cJSON进阶技巧
-
批量添加节点:使用循环批量添加数组元素或对象字段,适合动态数据:
// 批量添加数组元素(示例:成绩列表) int scores[] = {85, 92, 78, 95}; cJSON *scoreArr = cJSON_CreateArray(); for (int i = 0; i < 4; i++) { cJSON_AddItemToArray(scoreArr, cJSON_CreateNumber(scores[i])); } -
错误处理优化:解析时结合类型判断,避免空指针异常:
cJSON *age = cJSON_GetObjectItem(root, "age"); if (age != NULL && cJSON_IsNumber(age)) { printf("年龄: %d\n", cJSON_GetValueInt(age)); } else { printf("年龄字段不存在或类型错误\n"); } -
文件读写:结合文件操作,解析JSON文件或保存JSON字符串到文件:
// 从文件读取JSON FILE *fp = fopen("config.json", "r"); fseek(fp, 0, SEEK_END); long len = ftell(fp); char *buf = malloc(len + 1); fseek(fp, 0, SEEK_SET); fread(buf, 1, len, fp); buf[len] = '\0'; cJSON *root = cJSON_Parse(buf); fclose(fp); free(buf);
常见问题排查
- 内存泄漏:忘记调用
cJSON_Delete或free(cJSON_Print返回值),需确保所有动态分配的资源释放。 - 解析失败:检查JSON字符串是否有语法错误(如多余逗号、单引号键名),用
cJSON_GetErrorPtr()定位错误位置。 - 类型错误:提取数据前未判断节点类型(如将数字节点当作字符串取值),需先用
cJSON_IsXXX函数验证。 - 线程安全:cJSON库非线程安全,多线程环境下操作同一cJSON对象需加互斥锁。
5.4 拓展:LVGL显示天气信息(思路)
- 数据准备:通过API获取天气JSON数据(例:温度、湿度、天气状况、未来预报)。
- JSON解析:用cJSON解析JSON字符串,提取关键字段(如
"temp": 25、"condition": "晴天")。 - LVGL组件创建:
- 创建标签(Label)显示温度、湿度、天气状况。
- 创建列表(List)显示未来3天预报。
- 创建图片(Image)显示天气图标(根据
condition字段匹配图标)。
- 数据绑定:将解析后的JSON字段值设置到LVGL组件,实现动态显示。
- 更新机制:定时调用API获取最新天气数据,重新解析并更新LVGL组件内容。

浙公网安备 33010602011771号