c语言本身没有内置的json解析相关的标准库,要实现json数据的解析,通常需要借助第三方开源库,其中cJSON是应用最广泛、轻量且易用的选择,它仅由两个文件组成,适配绝大多数c语言开发环境。

cJSON库的基础准备
首先需要获取cJSON的源码,它包含两个文件:cJSON.h和cJSON.c,将其添加到自己的项目工程中即可使用。如果是Linux环境,也可以通过包管理器安装对应的开发包,安装完成后在代码中引入头文件就可以调用相关接口。
#include <stdio.h>
#include <stdlib.h>
// 引入cJSON头文件
#include "cJSON.h"
int main() {
// 后续解析逻辑会在这里编写
return 0;
}
解析基础类型的json数据
json的基础类型包括字符串、数字、布尔值、null,cJSON提供了对应的接口来获取这些类型的值,解析前需要先调用cJSON_Parse函数将json字符串转换为cJSON对象,解析完成后要记得释放内存。
解析字符串和数字示例
假设我们有一个简单的json字符串,包含用户名和年龄两个字段,解析代码如下:
#include <stdio.h>
#include <stdlib.h>
#include "cJSON.h"
int main() {
// 待解析的json字符串
char *json_str = "{"name":"张三","age":25}";
// 将json字符串解析为cJSON对象
cJSON *root = cJSON_Parse(json_str);
if (root == NULL) {
printf("json解析失败,错误位置:%sn", cJSON_GetErrorPtr());
return 1;
}
// 获取name字段,判断类型是否为字符串
cJSON *name_obj = cJSON_GetObjectItem(root, "name");
if (name_obj != NULL && name_obj->type == cJSON_String) {
printf("用户名:%sn", name_obj->valuestring);
}
// 获取age字段,判断类型是否为数字
cJSON *age_obj = cJSON_GetObjectItem(root, "age");
if (age_obj != NULL && age_obj->type == cJSON_Number) {
printf("年龄:%dn", age_obj->valueint);
}
// 释放cJSON对象内存
cJSON_Delete(root);
return 0;
}
解析复杂结构的json数据
实际场景中的json往往包含数组、嵌套对象等复杂结构,cJSON同样提供了对应的接口处理这类情况,比如获取数组大小、遍历数组元素、获取嵌套对象的字段等。
解析数组和嵌套对象示例
下面是一个包含嵌套对象和数组的json示例,解析其所有内容:
#include <stdio.h>
#include <stdlib.h>
#include "cJSON.h"
int main() {
// 包含嵌套对象和数组的json字符串
char *json_str = "{"user":{"name":"李四","age":30},"hobbies":["篮球","阅读","编程"],"is_student":false}";
cJSON *root = cJSON_Parse(json_str);
if (root == NULL) {
printf("json解析失败n");
return 1;
}
// 解析嵌套的user对象
cJSON *user_obj = cJSON_GetObjectItem(root, "user");
if (user_obj != NULL) {
cJSON *name = cJSON_GetObjectItem(user_obj, "name");
cJSON *age = cJSON_GetObjectItem(user_obj, "age");
if (name != NULL && name->type == cJSON_String) {
printf("用户姓名:%sn", name->valuestring);
}
if (age != NULL && age->type == cJSON_Number) {
printf("用户年龄:%dn", age->valueint);
}
}
// 解析hobbies数组
cJSON *hobbies_arr = cJSON_GetObjectItem(root, "hobbies");
if (hobbies_arr != NULL && hobbies_arr->type == cJSON_Array) {
int arr_size = cJSON_GetArraySize(hobbies_arr);
printf("爱好列表(共%d项):n", arr_size);
for (int i = 0; i < arr_size; i++) {
cJSON *item = cJSON_GetArrayItem(hobbies_arr, i);
if (item != NULL && item->type == cJSON_String) {
printf(" 第%d项:%sn", i+1, item->valuestring);
}
}
}
// 解析布尔值字段
cJSON *is_student = cJSON_GetObjectItem(root, "is_student");
if (is_student != NULL && is_student->type == cJSON_False) {
printf("是否为学生:否n");
} else if (is_student != NULL && is_student->type == cJSON_True) {
printf("是否为学生:是n");
}
cJSON_Delete(root);
return 0;
}
解析过程中的注意事项
- 每次调用
cJSON_Parse得到cJSON对象后,都必须调用cJSON_Delete释放内存,否则会造成内存泄漏。 - 获取字段前一定要先判断字段是否存在,以及字段的类型是否符合预期,避免访问空指针或者错误类型的数据导致程序崩溃。
- 如果json字符串格式错误,
cJSON_Parse会返回NULL,此时可以通过cJSON_GetErrorPtr获取错误发生的位置,方便排查问题。 - cJSON默认支持UTF-8编码的json字符串,如果处理的json包含其他编码,需要先转换为UTF-8再进行解析。
常见问题解答
如果json字段不存在会怎么样
cJSON_GetObjectItem函数在字段不存在时会返回NULL,因此一定要先做非空判断再使用返回值,否则会出现段错误。
解析大文件json需要注意什么
cJSON是将整个json加载到内存中解析的,如果json文件非常大,可能会占用过多内存,这种场景下可以考虑使用流式解析的库,或者分块读取json内容处理。
使用cJSON解析json时,所有操作都是基于cJSON对象树进行的,理解对象树的结构可以帮助你更快速地掌握复杂json的解析逻辑。
简单封装解析函数示例
为了更方便地获取json中的字符串字段,可以封装一个简单的函数,减少重复的判断逻辑:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "cJSON.h"
// 封装获取json字符串字段的函数,不存在或者类型错误返回默认字符串
char* get_json_string(cJSON *root, const char *key, const char *default_val) {
if (root == NULL || key == NULL) {
return (char*)default_val;
}
cJSON *item = cJSON_GetObjectItem(root, key);
if (item != NULL && item->type == cJSON_String) {
return item->valuestring;
}
return (char*)default_val;
}
int main() {
char *json_str = "{"title":"c语言json解析教程","author":"技术开发者"}";
cJSON *root = cJSON_Parse(json_str);
if (root == NULL) {
return 1;
}
char *title = get_json_string(root, "title", "默认标题");
char *author = get_json_string(root, "author", "未知作者");
printf("标题:%sn作者:%sn", title, author);
cJSON_Delete(root);
return 0;
}