配置文件是嵌入式开发中常用的参数管理方式。比如一个 config.json 可能包含各种类型的配置项:
{
/*浮点数*/
"temp_max" : 28.5, /*温度最大值*/
"humi_max" : 85.0, /*湿度最大值*/
/*整数*/
"brightness_max" : 75, /*亮度最大值*/
/*数组*/
"repeat" : [1, 3, 5, 7], /*重复周期*/
/*字符串*/
"device_name" : "room_test"
}
配置文件涵盖了整数、浮点数、字符串、整型数组等常见类型。如何用 C语言 来读取这些数据,甚至支持动态修改配置?下面通过 cJSON 库来实现一个交互式的配置工具。
1 代码逻辑
1.1 配置文件准备与数据结构定义

首先定义一个结构体来映射配置文件中的内容:
#define CONFIG_FILE "config.json"
// 配置文件数据,对应配置文件中的内容
typedef struct {
// 浮点数
double tempMax;
double humiMax;
// 整数
int brightnessMax;
// 数组
int repeat[7];
// 字符串
char deviceName[128];
} ConfigData_t;
1.2 文件读取与解析
// 全局 cJSON 对象,代表内存中的 JSON
static cJSON *g_root = NULL;
// 全局配置文件数据
ConfigData_t g_configData = {0};
// 从文件加载 JSON 到内存
int load_json(void)
{
FILE *fp = fopen(CONFIG_FILE, "r");
if (!fp) {
printf("fopen:%s failed\n", CONFIG_FILE);
return -1;
}
fseek(fp, 0, SEEK_END);
long len = ftell(fp);
fseek(fp, 0, SEEK_SET);
char *buf = malloc(len + 1);
fread(buf, 1, len, fp);
fclose(fp);
cJSON_Minify(buf); // 删除注释+压缩
g_root = cJSON_Parse(buf);
free(buf);
if (!g_root) {
printf("JSON parse err: %s\n", cJSON_GetErrorPtr());
return -1;
}
// 解析json中各字段的数据
cJSON *jData = NULL;
if ((jData = cJSON_GetObjectItem(g_root, "temp_max"))) {
g_configData.tempMax = jData->valuedouble;
}
if ((jData = cJSON_GetObjectItem(g_root, "humi_max"))) {
g_configData.humiMax = jData->valuedouble;
}
if ((jData = cJSON_GetObjectItem(g_root, "brightness_max"))) {
g_configData.brightnessMax = jData->valueint;
}
if ((jData = cJSON_GetObjectItem(g_root, "device_name"))) {
if (jData->valuestring) {
strncpy(g_configData.deviceName, jData->valuestring, sizeof(g_configData.deviceName));
}
}
if ((jData = cJSON_GetObjectItem(g_root, "repeat"))) {
cJSON *jDataItem = NULL;
if (cJSON_IsArray(jData)) {
int i = 0;
cJSON_ArrayForEach(jDataItem, jData)
{
g_configData.repeat[i++] = jDataItem->valueint;
}
}
}
printf(" tempMax:%.2f\n humiMax:%.2f\n brightnessMax:%d\n deviceName:%s\n",
g_configData.tempMax, g_configData.humiMax, g_configData.brightnessMax, g_configData.deviceName);
for (int i = 0; i < (sizeof(g_configData.repeat) / sizeof(g_configData.repeat[0])); i++) {
printf("repeat[%d]:%d\n", i, g_configData.repeat[i]);
}
printf("parse %s ok\n", CONFIG_FILE);
return 0;
}
1.3 JSON 整体数据的打印
使用 cJSON_Print 可以直观地输出当前 JSON 树的内容:
// 打印当前全部配置
void show_config(void)
{
if (!g_root) return;
char *text = cJSON_Print(g_root);
printf("\n======== current config ========\n");
printf("%s\n", text);
cJSON_free(text);
printf("==========================\n");
}
1.4 根据字段的名称修改 JSON 数据
在修改前,需要先确认原有字段的类型,避免将数值类型误改成字符串,导致后续逻辑出错。
注意 cJSON 中,int 和 double 是统一的 number 类型,内部同时保存了 valueint 和 valuedouble。
1.4.1 判断字段的类型
为了简化处理,这里将 int 和 double 都归为自定义的 DATA_TYPE_DOUBLE:
typedef enum {
DATA_TYPE_ERR = 0,
DATA_TYPE_INT,
DATA_TYPE_DOUBLE,
DATA_TYPE_STRING
} DataType_t;
// 判断数据的类型
DataType_t check_value_type(const char *key)
{
DataType_t dataType = DATA_TYPE_ERR;
cJSON *node = cJSON_GetObjectItemCaseSensitive(g_root, key);
if (node) {
if (cJSON_IsNumber(node)) {
printf("key:%s int:%d double:%f\n", key, node->valueint, node->valuedouble);
// 整数和浮点数,统一按double处理
dataType = DATA_TYPE_DOUBLE;
printf("key:%s type is double\n", key);
} else if (cJSON_IsString(node)) {
dataType = DATA_TYPE_STRING;
printf("key:%s type is string\n", key);
} else {
printf("key:%s type is err\n", key);
}
}
return dataType;
}
1.4.2 修改字段的值
根据类型调用不同的 cJSON API 修改值,数据通过 void * 传入并由 dataType 区分:
// 修改字段的值
void update_item_value(const char *key, void *val, DataType_t dataType)
{
cJSON *node = cJSON_GetObjectItemCaseSensitive(g_root, key);
if (node) {
if (DATA_TYPE_INT == dataType) {
if (cJSON_IsNumber(node)) {
cJSON_SetNumberValue(node, *(int *)(val));
printf("modify [%s] = %d\n", key, *(int *)(val));
}
} else if (DATA_TYPE_DOUBLE == dataType) {
if (cJSON_IsNumber(node)) {
cJSON_SetNumberValue(node, *(double *)(val));
printf("modify [%s] = %.2f\n", key, *(double *)(val));
}
} else if (DATA_TYPE_STRING == dataType) {
if (cJSON_IsString(node)) {
cJSON_SetValuestring(node, (char *)(val));
printf("modify [%s] = %s\n", key, (char *)val);
}
}
} else {
printf("key [%s] not exist or not num\n\n", key);
}
}
1.4.3 修改数组中的值
数组的修改不能用上面的通用函数,需要单独处理,通过索引定位并改写:
// 修改 int 数组中指定下标的值
void set_array_item(const char *arr_key, int index, int val)
{
cJSON *arr = cJSON_GetObjectItemCaseSensitive(g_root, arr_key);
if (!arr || !cJSON_IsArray(arr)) {
printf("数组 %s 不存在\n", arr_key);
return;
}
int size = cJSON_GetArraySize(arr);
if (index < 0 || index >= size) {
printf("下标越界,数组长度: %d\n", size);
return;
}
cJSON *item = cJSON_GetArrayItem(arr, index);
if (item && cJSON_IsNumber(item)) {
cJSON_SetNumberValue(item, val);
printf("%s[%d] = %d\n", arr_key, index, val);
}
}
1.5 JSON 保存到文件
将内存中的 JSON 树通过 cJSON_Print 序列化为字符串后写回文件:
// 保存内存中的 JSON 到文件
int save_json(void)
{
if (!g_root) {
return -1;
}
char *text = cJSON_Print(g_root);
if (!text) {
return -1;
}
FILE *fp = fopen(CONFIG_FILE, "w");
if (!fp) {
free(text);
return -1;
}
fputs(text, fp);
fclose(fp);
cJSON_free(text);
printf("save to %s\n", CONFIG_FILE);
return 0;
}
1.6 一个交互式的配置工具
为了能在命令行中方便地查看和修改配置,实现一个简单的菜单驱动交互界面:
// 交互式菜单
void interactive_menu(void)
{
int choice;
while (1) {
printf("\n===== 交互式配置工具 =====\n");
printf("1. 显示当前配置\n");
printf("2. 修改非数组key的值\n");
printf("3. 修改数组key[int]的值\n");
printf("4. 保存并退出\n");
printf("5. 不保存退出\n");
printf("请输入指令: ");
if (scanf("%d", &choice) != 1) {
// 清空输入缓冲区
while (getchar() != '\n');
printf("输入无效\n");
continue;
}
switch (choice) {
case 1: {
show_config();
break;
}
case 2: {
char key[256] = {0};
printf("输入要修正的非数组字段名称: ");
scanf("%s", key);
DataType_t dataType = check_value_type(key);
if (DATA_TYPE_ERR == dataType) {
printf("配置文件中的数据类型错误\n");
} else {
printf("输入要修改的值:");
if (DATA_TYPE_INT == dataType) {
int valueInt = 0;
scanf("%d", &valueInt);
update_item_value(key, &valueInt, DATA_TYPE_INT);
} else if (DATA_TYPE_DOUBLE == dataType) {
double valuedouble = 0.0;
scanf("%lf", &valuedouble);
update_item_value(key, &valuedouble, DATA_TYPE_DOUBLE);
} else if (DATA_TYPE_STRING == dataType) {
char valueStr[256] = {0};
scanf("%s", valueStr);
update_item_value(key, &valueStr, DATA_TYPE_STRING);
}
}
break;
}
case 3: {
int idx, val;
printf("输入数组下标: ");
scanf("%d", &idx);
printf("输入新值: ");
scanf("%d", &val);
set_array_item("repeat", idx, val);
break;
}
case 4: {
save_json();
cJSON_Delete(g_root);
return;
}
case 5: {
cJSON_Delete(g_root);
printf("已退出,未保存\n");
return;
}
default:
printf("无效选项\n");
break;
}
}
}
2 代码测试
2.1 运行查看配置数据

2.2 修改 number 类型的值

2.3 修改字符串类型的值

2.4 修改数组的值

2.5 保存数据

查看最终保存的配置文件内容:

3 完整代码
// gcc config_json_test.c cjson/cJSON.c -o config_json_test
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "cjson/cJSON.h"
#define CONFIG_FILE "config.json"
// 配置文件数据,对应配置文件中的内容
typedef struct {
// 浮点数
double tempMax;
double humiMax;
// 整数
int brightnessMax;
// 数组
int repeat[7];
// 字符串
char deviceName[128];
} ConfigData_t;
typedef enum {
DATA_TYPE_ERR = 0,
DATA_TYPE_INT,
DATA_TYPE_DOUBLE,
DATA_TYPE_STRING
} DataType_t;
// 全局 cJSON 对象,代表内存中的 JSON
static cJSON *g_root = NULL;
// 全局配置文件数据
ConfigData_t g_configData = {0};
// 从文件加载 JSON 到内存
int load_json(void)
{
FILE *fp = fopen(CONFIG_FILE, "r");
if (!fp) {
printf("fopen:%s failed\n", CONFIG_FILE);
return -1;
}
fseek(fp, 0, SEEK_END);
long len = ftell(fp);
fseek(fp, 0, SEEK_SET);
char *buf = malloc(len + 1);
fread(buf, 1, len, fp);
fclose(fp);
cJSON_Minify(buf); // 删除注释+压缩
g_root = cJSON_Parse(buf);
free(buf);
if (!g_root) {
printf("JSON parse err: %s\n", cJSON_GetErrorPtr());
return -1;
}
// 解析json中各字段的数据
cJSON *jData = NULL;
if ((jData = cJSON_GetObjectItem(g_root, "temp_max"))) {
g_configData.tempMax = jData->valuedouble;
}
if ((jData = cJSON_GetObjectItem(g_root, "humi_max"))) {
g_configData.humiMax = jData->valuedouble;
}
if ((jData = cJSON_GetObjectItem(g_root, "brightness_max"))) {
g_configData.brightnessMax = jData->valueint;
}
if ((jData = cJSON_GetObjectItem(g_root, "device_name"))) {
if (jData->valuestring) {
strncpy(g_configData.deviceName, jData->valuestring, sizeof(g_configData.deviceName));
}
}
if ((jData = cJSON_GetObjectItem(g_root, "repeat"))) {
cJSON *jDataItem = NULL;
if (cJSON_IsArray(jData)) {
int i = 0;
cJSON_ArrayForEach(jDataItem, jData)
{
g_configData.repeat[i++] = jDataItem->valueint;
}
}
}
printf(" tempMax:%.2f\n humiMax:%.2f\n brightnessMax:%d\n deviceName:%s\n",
g_configData.tempMax, g_configData.humiMax, g_configData.brightnessMax, g_configData.deviceName);
for (int i = 0; i < (sizeof(g_configData.repeat) / sizeof(g_configData.repeat[0])); i++) {
printf("repeat[%d]:%d\n", i, g_configData.repeat[i]);
}
printf("parse %s ok\n", CONFIG_FILE);
return 0;
}
// 保存内存中的 JSON 到文件
int save_json(void)
{
if (!g_root) {
return -1;
}
char *text = cJSON_Print(g_root);
if (!text) {
return -1;
}
FILE *fp = fopen(CONFIG_FILE, "w");
if (!fp) {
free(text);
return -1;
}
fputs(text, fp);
fclose(fp);
cJSON_free(text);
printf("save to %s\n", CONFIG_FILE);
return 0;
}
// 打印当前全部配置
void show_config(void)
{
if (!g_root) return;
char *text = cJSON_Print(g_root);
printf("\n======== current config ========\n");
printf("%s\n", text);
cJSON_free(text);
printf("==========================\n");
}
// 判断数据的类型
DataType_t check_value_type(const char *key)
{
DataType_t dataType = DATA_TYPE_ERR;
cJSON *node = cJSON_GetObjectItemCaseSensitive(g_root, key);
if (node) {
if (cJSON_IsNumber(node)) {
printf("key:%s int:%d double:%f\n", key, node->valueint, node->valuedouble);
// 整数和浮点数,统一按double处理
dataType = DATA_TYPE_DOUBLE;
printf("key:%s type is double\n", key);
} else if (cJSON_IsString(node)) {
dataType = DATA_TYPE_STRING;
printf("key:%s type is string\n", key);
} else {
printf("key:%s type is err\n", key);
}
}
return dataType;
}
// 修改字段的值
void update_item_value(const char *key, void *val, DataType_t dataType)
{
cJSON *node = cJSON_GetObjectItemCaseSensitive(g_root, key);
if (node) {
if (DATA_TYPE_INT == dataType) {
if (cJSON_IsNumber(node)) {
cJSON_SetNumberValue(node, *(int *)(val));
printf("modify [%s] = %d\n", key, *(int *)(val));
}
} else if (DATA_TYPE_DOUBLE == dataType) {
if (cJSON_IsNumber(node)) {
cJSON_SetNumberValue(node, *(double *)(val));
printf("modify [%s] = %.2f\n", key, *(double *)(val));
}
} else if (DATA_TYPE_STRING == dataType) {
if (cJSON_IsString(node)) {
cJSON_SetValuestring(node, (char *)(val));
printf("modify [%s] = %s\n", key, (char *)val);
}
}
} else {
printf("key [%s] not exist or not num\n\n", key);
}
}
// 修改 int 数组中指定下标的值
void set_array_item(const char *arr_key, int index, int val)
{
cJSON *arr = cJSON_GetObjectItemCaseSensitive(g_root, arr_key);
if (!arr || !cJSON_IsArray(arr)) {
printf("数组 %s 不存在\n", arr_key);
return;
}
int size = cJSON_GetArraySize(arr);
if (index < 0 || index >= size) {
printf("下标越界,数组长度: %d\n", size);
return;
}
cJSON *item = cJSON_GetArrayItem(arr, index);
if (item && cJSON_IsNumber(item)) {
cJSON_SetNumberValue(item, val);
printf("%s[%d] = %d\n", arr_key, index, val);
}
}
// 交互式菜单
void interactive_menu(void)
{
int choice;
while (1) {
printf("\n===== 交互式配置工具 =====\n");
printf("1. 显示当前配置\n");
printf("2. 修改非数组key的值\n");
printf("3. 修改数组key[int]的值\n");
printf("4. 保存并退出\n");
printf("5. 不保存退出\n");
printf("请输入指令: ");
if (scanf("%d", &choice) != 1) {
// 清空输入缓冲区
while (getchar() != '\n');
printf("输入无效\n");
continue;
}
switch (choice) {
case 1: {
show_config();
break;
}
case 2: {
char key[256] = {0};
printf("输入要修正的非数组字段名称: ");
scanf("%s", key);
DataType_t dataType = check_value_type(key);
if (DATA_TYPE_ERR == dataType) {
printf("配置文件中的数据类型错误\n");
} else {
printf("输入要修改的值:");
if (DATA_TYPE_INT == dataType) {
int valueInt = 0;
scanf("%d", &valueInt);
update_item_value(key, &valueInt, DATA_TYPE_INT);
} else if (DATA_TYPE_DOUBLE == dataType) {
double valuedouble = 0.0;
scanf("%lf", &valuedouble);
update_item_value(key, &valuedouble, DATA_TYPE_DOUBLE);
} else if (DATA_TYPE_STRING == dataType) {
char valueStr[256] = {0};
scanf("%s", valueStr);
update_item_value(key, &valueStr, DATA_TYPE_STRING);
}
}
break;
}
case 3: {
int idx, val;
printf("输入数组下标: ");
scanf("%d", &idx);
printf("输入新值: ");
scanf("%d", &val);
set_array_item("repeat", idx, val);
break;
}
case 4: {
save_json();
cJSON_Delete(g_root);
return;
}
case 5: {
cJSON_Delete(g_root);
printf("已退出,未保存\n");
return;
}
default:
printf("无效选项\n");
break;
}
}
}
int main()
{
if (load_json() != 0) {
printf("load config file err!\n");
return -1;
}
interactive_menu();
return 0;
}
4 总结
本文通过一个实际例子展示了如何使用 cJSON 将 JSON 文件作为配置文件进行读取和动态修改。从结构体定义、文件解析到类型的判断与值的修改,每一步都给出了清晰的代码实现,并在此基础上搭建了一个简易的交互式配置工具。掌握这些方法后,你可以在嵌入式或桌面应用中便捷地处理 JSON 格式的配置数据。