JSON

前言

JSON是轻量级数据交换格式,兼具人类可读性和机器解析效率,广泛用于API数据传输、配置文件等场景。cJSON是C语言环境下的轻量JSON解析库,提供完整的JSON构建、解析、修改接口,本文整合JSON基础、cJSON核心用法及实战示例,补充拓展知识,方便日常开发查阅。


JSON基础

核心结构

JSON有两种基础结构,可组合嵌套形成复杂数据:

  • 对象:用{}包裹,由键值对组成。键名必须用双引号包裹,键值间用:分隔,键值对间用,分隔。
  • 数组:用[]包裹,是有序的值列表,值间用,分隔。

支持的数据类型

  • 字符串:用双引号包裹(例:"name": "Alice"
  • 数字:整数或浮点数(例:12312.34
  • 布尔值:truefalse
  • 空值:null
  • 对象:嵌套的{}结构
  • 数组:嵌套的[]结构

完整示例

{
  "name": "John Doe",
  "age": 30,
  "isEmployed": true,
  "address": {
    "street": "123 Main St",
    "city": "New York"
  },
  "hobbies": ["reading", "hiking", "coding"]
}

核心特点

  1. 轻量级:比XML更简洁,数据传输效率更高
  2. 易读性:结构清晰,人类可直接阅读编辑
  3. 跨语言支持:几乎所有编程语言都有解析/生成库
  4. 用途广泛: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=c89
    
    修改后执行make,生成ARM平台可用库文件

直接使用源码文件

无需编译库,直接将解压后的json.cjson.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进阶技巧

  1. 批量添加节点:使用循环批量添加数组元素或对象字段,适合动态数据:

    // 批量添加数组元素(示例:成绩列表)
    int scores[] = {85, 92, 78, 95};
    cJSON *scoreArr = cJSON_CreateArray();
    for (int i = 0; i < 4; i++) {
        cJSON_AddItemToArray(scoreArr, cJSON_CreateNumber(scores[i]));
    }
    
  2. 错误处理优化:解析时结合类型判断,避免空指针异常:

    cJSON *age = cJSON_GetObjectItem(root, "age");
    if (age != NULL && cJSON_IsNumber(age)) {
        printf("年龄: %d\n", cJSON_GetValueInt(age));
    } else {
        printf("年龄字段不存在或类型错误\n");
    }
    
  3. 文件读写:结合文件操作,解析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);
    

常见问题排查

  1. 内存泄漏:忘记调用cJSON_Deletefree(cJSON_Print返回值),需确保所有动态分配的资源释放。
  2. 解析失败:检查JSON字符串是否有语法错误(如多余逗号、单引号键名),用cJSON_GetErrorPtr()定位错误位置。
  3. 类型错误:提取数据前未判断节点类型(如将数字节点当作字符串取值),需先用cJSON_IsXXX函数验证。
  4. 线程安全:cJSON库非线程安全,多线程环境下操作同一cJSON对象需加互斥锁。

5.4 拓展:LVGL显示天气信息(思路)

  1. 数据准备:通过API获取天气JSON数据(例:温度、湿度、天气状况、未来预报)。
  2. JSON解析:用cJSON解析JSON字符串,提取关键字段(如"temp": 25"condition": "晴天")。
  3. LVGL组件创建
    • 创建标签(Label)显示温度、湿度、天气状况。
    • 创建列表(List)显示未来3天预报。
    • 创建图片(Image)显示天气图标(根据condition字段匹配图标)。
  4. 数据绑定:将解析后的JSON字段值设置到LVGL组件,实现动态显示。
  5. 更新机制:定时调用API获取最新天气数据,重新解析并更新LVGL组件内容。

posted @ 2025-12-13 09:00  Jaklin  阅读(77)  评论(0)    收藏  举报