找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

5288

积分

0

好友

747

主题
发表于 1 小时前 | 查看: 4| 回复: 0

配置文件是嵌入式开发中常用的参数管理方式。比如一个 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 配置文件准备与数据结构定义

配置文件示例,展示浮点数、整数、数组和字符串字段的JSON结构

首先定义一个结构体来映射配置文件中的内容:

#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 类型,内部同时保存了 valueintvaluedouble

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 运行查看配置数据

编译运行后首次解析JSON并打印所有配置参数的终端输出

2.2 修改 number 类型的值

使用交互式工具修改浮点字段 temp_max 从 28.5 到 55.5 的过程

2.3 修改字符串类型的值

将 device_name 从 room_test 修改为 room2 并显示更新后的配置

2.4 修改数组的值

修改整型数组 repeat 下标0的值从 1 改为 2

2.5 保存数据

执行保存并退出操作,数据被写回 config.json

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

保存后 config.json 的实际内容,确认各项修改均已生效

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 格式的配置数据。




上一篇:libcurl 读回调实战:C 语言文件分块上传实现
下一篇:Docker Swarm迁移Kubernetes实录:这6个大坑千万别踩
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-8-3 05:19 , Processed in 1.656091 second(s), 42 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表