使用cJosn将数据读写文件
本篇来介绍如何将json数据写入文件,并支持对json文件中的字段进行修改。
1 cJSON
本篇使用cJSON来演示,cJSON 是一个轻量级的 C 语言 JSON 解析库,API 简洁易用,核心功能围绕 JSON 的创建、解析、修改和序列化展开。
需要先下载cJSON库:https://github.com/DaveGamble/cJSON
只需要引入两个文件:
- cJSON.c
- cJSON.h
使用cJSON来操作 json数据,需要先了解cJSON的API接口。
1.1 cJSON常用API
1.1.1 json对象 / 数组的创建
| API 函数 | 功能说明 |
|---|---|
cJSON *cJSON_CreateObject(void) | 创建一个空的 JSON 对象({}),返回指向该对象的指针 |
cJSON *cJSON_CreateArray(void) | 创建一个空的 JSON 数组([]),返回指向该数组的指针 |
cJSON *cJSON_CreateString(const char *string) | 创建一个字符串类型的 JSON 元素(如 "name": "张三" 中的值) |
cJSON *cJSON_CreateNumber(double num) | 创建一个数字类型的 JSON 元素(支持整数、浮点数) |
cJSON *cJSON_CreateBool(cJSON_bool boolean) | 创建一个布尔类型的 JSON 元素(true 或 false,参数为 1 或 0) |
cJSON *cJSON_CreateNull(void) | 创建一个 null 类型的 JSON 元素 |
1.1.2 向对象 / 数组添加元素
| API 函数 | 功能说明 |
|---|---|
void cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item) | 向 JSON 对象添加一个键值对(string 为键,item 为值) |
void cJSON_AddItemToArray(cJSON *array, cJSON *item) | 向 JSON 数组添加一个元素(无需键,按顺序排列) |
cJSON *cJSON_AddStringToObject(cJSON *object, const char *name, const char *string)**** | 向对象添加字符串类型的键值对(内部自动创建字符串元素) |
cJSON *cJSON_AddNumberToObject(cJSON *object, const char *name, double number) | 向对象添加数字类型的键值对 |
cJSON *cJSON_AddBoolToObject(cJSON *object, const char *name, cJSON_bool boolean) | 向对象添加布尔类型的键值对 |
1.1.3 JSON 解析(从字符串到对象)
| API 函数 | 功能说明 |
|---|---|
cJSON *cJSON_Parse(const char *value) | 解析 JSON 字符串,返回根对象 / 数组指针。若解析失败,返回 NULL |
const char *cJSON_GetErrorPtr(void) | 当 cJSON_Parse 失败时,调用此函数获取错误位置的描述(辅助调试) |
1.1.4 读取 JSON 元素(获取字段值)
| API 函数 | 功能说明 |
|---|---|
cJSON *cJSON_GetObjectItem(const cJSON *object, const char *string) | 从 JSON 对象中获取指定键对应的元素(返回元素指针,若不存在则为 NULL) |
cJSON *cJSON_GetArrayItem(const cJSON *array, int index) | 从 JSON 数组中获取指定索引(index)的元素(索引从 0 开始) |
int cJSON_GetArraySize(const cJSON *array) | 获取 JSON 数组的长度(元素个数) |
const char *cJSON_GetStringValue(const cJSON *item) | 从字符串类型的元素中获取字符串值(需先确认元素类型为 cJSON_String) |
double cJSON_GetNumberValue(const cJSON *item) | 从数字类型的元素中获取数值(需先确认元素类型为 cJSON_Number) |
cJSON_bool cJSON_IsBool(const cJSON *item) | 判断元素是否为布尔类型(返回 1 是,0 否) |
cJSON_bool cJSON_IsNull(const cJSON *item) | 判断元素是否为 null 类型 |
1.1.5 修改 JSON 元素
| API 函数 | 功能说明 |
|---|---|
void cJSON_SetValuestring(cJSON *item, const char *string) | 将元素的值修改为字符串(需元素原本为字符串类型,或强制覆盖) |
void cJSON_SetNumberValue(cJSON *item, double number) | 将元素的值修改为数字 |
void cJSON_SetBoolValue(cJSON *item, cJSON_bool boolean) | 将元素的值修改为布尔值 |
1.1.6 JSON 序列化(从对象到字符串)
| API 函数 | 功能说明 |
|---|---|
char *cJSON_Print(const cJSON *item) | 将 JSON 对象 / 数组转换为带缩进的格式化字符串(可读性好,体积较大) |
char *cJSON_PrintUnformatted(const cJSON *item) | 转换为无缩进的紧凑字符串(体积小,适合传输) |
char *cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt) | 带缓冲的序列化,prebuffer 为预分配缓冲区大小,fmt 为 1 时格式化 |
1.1.7 内存释放
| API 函数 | 功能说明 |
|---|---|
void cJSON_Delete(cJSON *item) | 递归释放一个 JSON 对象 / 数组及其所有子元素占用的内存(根对象释放后,整个结构都被释放) |
1.1.8 其它
| API 函数 | 功能说明 |
|---|---|
cJSON *cJSON_Duplicate(const cJSON *item, cJSON_bool recurse) | 复制一个 JSON 元素(recurse=1 时递归复制子元素)。 |
void cJSON_ReplaceItemInObject(cJSON *object, const char *string, cJSON *newitem) | 替换对象中指定键的元素(旧元素会被自动释放)。 |
void cJSON_ReplaceItemInArray(cJSON *array, int index, cJSON *newitem) | 替换数组中指定索引的元素。 |
1.2 创建json示例
这里示例创建一个json对象,并添加一些不同类型的字段:
- cJSON_CreateObject:创建json对象
- cJSON_AddStringToObject:添加字符串类型的数据
- cJSON_AddNumberToObject:添加数值类型的数据
- cJSON_AddBoolToObject:添加BOOL类型的数据
int test_create_a_json_object(char **json_str)
{// 创建JSONcJSON *root = cJSON_CreateObject();if (root == NULL) {PRINT("cJSON_CreateObject err!\n");return -1;}// 向根对象添加字段(支持字符串、数字、布尔等类型)cJSON_AddStringToObject(root, "name", "张三"); // 字符串字段cJSON_AddNumberToObject(root, "age", 22); // 数字字段cJSON_AddBoolToObject(root, "is_student", 1); // 布尔字段(0=false,1=true)cJSON_AddStringToObject(root, "major", "计算机科学"); // 待修改的示例字段// 将JSON对象转换为格式化字符串(带缩进,便于阅读)*json_str = cJSON_Print(root);if (json_str == NULL) {PRINT("cJSON_Print err!\n");cJSON_Delete(root); // 释放JSON对象内存,避免泄漏return -1;}PRINT("json_str:%s\n", *json_str);cJSON_Delete(root);return 0;
}
运行结果
[test_create_a_json_object][212] json_str:{"name": "张三","age": 22,"is_student": true,"major": "计算机科学"
}
2 文件操作
创建json数据后,如果想将其写入文件,可以先将json数据转换为字符串(cJSON_Print),然后再将其整个写入文件。
2.1 写入文件
// 将JSON数据写入文件
int json_write_to_file(const char *filename, char *json_str)
{if (NULL == filename || NULL == json_str){PRINT("NULL ptr\n");return -1;}// 打开文件("w"模式:覆盖写入,若文件不存在则创建)FILE *fp = fopen(filename, "w");if (fp == NULL) {PRINT("fopen %s err!\n", filename);return -1;}// 写入JSON字符串到文件fprintf(fp, "%s", json_str);// 释放资源fclose(fp);return 0;
}
2.2 修改json文件中的值
对于写入文件中的json数据,如果想要修改json中的数据,思路如下:
- 先将json数据从文件中整个读出来
- 然后对json数据进行解析(cJSON_Parse)
- 接着查找到要修改的字段,进行修改
- 最后再将整个json数据写回文件
对文件的读写的操作如下,函数的参数:
- const char *filename:要操作的json文件的指针
- const char *field_name:要修改的字段名称
- const char *new_value:要修改为的值
// 修改文件中JSON的指定字段(支持字符串/数字/布尔类型)
int jsonfile_modify_field(const char *filename, const char *field_name, const char *new_value)
{// 打开文件FILE *fp = fopen(filename, "r");if (fp == NULL) {PRINT("fopen %s err!\n", filename);return -1;}// 获取文件长度(用于分配缓冲区)fseek(fp, 0, SEEK_END);long file_len = ftell(fp);fseek(fp, 0, SEEK_SET);// 分配缓冲区(+1 用于存储字符串结束符 '\0')char *buffer = (char *)malloc(file_len + 1);if (buffer == NULL) {PRINT("malloc err!\n");fclose(fp);return -1;}// 读取文件内容到缓冲区fread(buffer, 1, file_len, fp);buffer[file_len] = '\0'; // 手动添加字符串结束符fclose(fp);// 解析缓冲区中的JSON字符串cJSON *root = cJSON_Parse(buffer);free(buffer); // 缓冲区已用完,释放内存if (root == NULL) {PRINT("cJSON_Parse err!\n");return -1;}// 修改JSON字段if (0 != json_modify_field(root, field_name, new_value)){PRINT("json_modify_field err!\n");cJSON_Delete(root);return -1;}// 将修改后的JSON重新写入文件(覆盖原内容)char *modified_json = cJSON_Print(root);if (modified_json == NULL) {PRINT("modify err!\n");cJSON_Delete(root);return -1;}// 以"w"模式打开文件(覆盖原内容)fp = fopen(filename, "w");if (fp == NULL) {PRINT("fopen %s err!\n", filename);free(modified_json);cJSON_Delete(root);return -1;}fprintf(fp, "%s", modified_json);PRINT("modify [%s] -> [%s] ok,now:\n%s\n", field_name, new_value, modified_json);// 释放所有资源fclose(fp);free(modified_json);cJSON_Delete(root);return 0;
}
具体的对json字段进行修改的逻辑,单独封装为json_modify_field
函数的参数:
- cJSON *root:要操作的json指针
- const char *field_name:要修改的字段名称
- const char *new_value:要修改为的值
这里用到的API接口
- cJSON_GetObjectItem:从 JSON 对象中获取指定键对应的元素,另外通过type还可判断字段的类型
- 对字段的值进行修改:
- cJSON_SetValuestring
- cJSON_SetNumberValue
- cJSON_SetBoolValue
// 修改JSON的指定字段(支持字符串/数字/布尔类型)
int json_modify_field(cJSON *root, const char *field_name, const char *new_value)
{// 查找指定字段,若存在则修改;若不存在则提示cJSON *target_field = cJSON_GetObjectItem(root, field_name);if (target_field == NULL) {PRINT("no find:%s\n", field_name);return -1;}// 获取原字段类型并修改int modify_ok = 0;switch (target_field->type) {// 原字段是字符串:直接用新值替换case cJSON_String:{cJSON_SetValuestring(target_field, new_value);modify_ok = 1;break;}// 原字段是数字:尝试将 new_value 转为数字case cJSON_Number:{char *endptr;double num_val = strtod(new_value, &endptr);// 检查转换是否成功(new_value 必须全是数字)if (*endptr == '\0') { // 没有多余字符,转换有效cJSON_SetNumberValue(target_field, num_val);modify_ok = 1;} else {PRINT("new value [%s] convert to num err\n", new_value);}break;}// 原字段是布尔值:将 new_value 转为 0(false)或 1(true)case cJSON_True:case cJSON_False:{// 支持 "0"/"false" 转为 false;"1"/"true" 转为 true(不区分大小写)if (strcmp(new_value, "0") == 0 || strcasecmp(new_value, "false") == 0) {cJSON_SetBoolValue(target_field, 0);modify_ok = 1;} else if (strcmp(new_value, "1") == 0 || strcasecmp(new_value, "true") == 0) {cJSON_SetBoolValue(target_field, 1);modify_ok = 1;} else {PRINT("new value [%s] convert to bool err\n", new_value);}break;}default:{// 不支持的类型(如数组、对象等)PRINT("err type: %d\n", target_field->type);break;}}if (!modify_ok) {return -1;}return 0;
}
3 整体测试
下面是整体测试的主函数,基本流程为:
- 创建json数据
- 写入文件
- 依次修改josn文件中不同类型的数据
在修改后,对文件的josn数据进行打印
// gcc test1.c cjson/cJSON.c -o test1 -lm
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include "cjson/cJSON.h"#define PRINT(fmt, ...) printf("[%s][%d] " fmt, __func__, __LINE__, ##__VA_ARGS__)int main()
{const char *filename = "user_info.json"; // 目标JSON文件PRINT("filename:%s\n", filename);char *json_str = NULL;if (0 != test_create_a_json_object(&json_str)){PRINT("test_create_a_json_object err!\n");return -1;}// 将JSON写入文件if (json_write_to_file(filename, json_str) != 0) {PRINT("json_write_to_file err!\n");return -1;}free(json_str);// 修改指定字段("age" 为 25)jsonfile_modify_field(filename, "age", "25");// 修改指定字段("is_srudent" 为 false)jsonfile_modify_field(filename, "is_student", "false");// 修改指定字段("major" 为 "嵌入式软件")jsonfile_modify_field(filename, "major", "嵌入式软件");return 0;
}
实际运行结果如下:

4 总结
本篇介绍了如何使用cJSON库以及借助文件操作,将json数据写入文件,并支持对文件中的json数据进行按字段的指定修改。
