使用cJosn读写配置文件

之前的文章,介绍过C语言对json文件的读写,本篇,继续来讨论一种使用场景,json作为配置文件,使用C语言来读取,并支持对配置的修改。

例如,配置文件config.json中的内容如下:

json 复制代码
{
    /*浮点数*/
    "temp_max" : 28.5, /*温度最大值*/
    "humi_max" : 85.0, /*湿度最大值*/
    
    /*整数*/
    "brightness_max" : 75, /*亮度最大值*/
    
    /*数组*/
    "repeat" : [1, 3, 5, 7], /*重复周期*/
    
    /*字符串*/
    "device_name" : "room_test"
}

配置文件中,包括常用的基本类型:

  • 整数
  • 浮点数
  • 字符串
  • 整型数组

如可用C语言来读取配置文件中的数据,并支持对配置的修改呢?下面来看代码逻辑分析

1 代码逻辑

1.1 配置文件准备与数据结构定义

配置文件的内容示例如下:

在代码中,定义结构体来存储从配置文件中读取到的数据:

c 复制代码
#define CONFIG_FILE "config.json"

// 配置文件数据,对应配置文件中的内容
typedef struct{
    // 浮点数
    double tempMax;
    double humiMax;
    // 整数
    int brightnessMax;
    // 数组
    int repeat[7];
    // 字符串
    char deviceName[128];
}ConfigData_t;

1.2 文件读取与解析

c 复制代码
// 全局 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;
    }
    
    // 解析josn中各字段的数据
    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整体数据的打印

为了方便观察json配置数据中的内容,可以使用cJSON_Print将数据打印出来:

c 复制代码
// 打印当前全部配置
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是通用的,其内部同时存了两个值:

  • valueint:int整数
  • valuedouble:double浮点数

它们都属于number类型

1.4.1 判断字段的类型

由于int和double是通用,代码这里暂且把int和double都归为自定义的DATA_TYPE_DOUBLE这一类:

c 复制代码
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 修改字段的值

根据字段的类型,使用对应的API接口修改不同类型的值。

这里将传入的数据使用void *来接收,并通过dataType来指示数据类型

c 复制代码
// 修改字段的值
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 修改数值中的值

数值中的值与上面的单个字段的值的修改逻辑不能通用,需要单独写一个处理函数:通过指定要修改的数组的位置索引,来修改数组中指定索引的值:

c 复制代码
// 修改 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 josn保存到文件

本实就是调用cJSON_Print得到文本格式的数据,然后通过fputs将内容写入到文件即可

c 复制代码
// 保存内存中的 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 一个交互式的配置工具

在命令行中,为了便于查看和修改配置文件,可以使用一个交互式的方式来查看修改

c 复制代码
// 交互式菜单
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.2 保存数据

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

3 完整代码

c 复制代码
// 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;
    }
    
    // 解析josn中各字段的数据
    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 总结

本篇介绍了一种json作为配置文件的读取与修改的简单示例,首先介绍了配置文件的结构以及读取和修改的代码逻辑,然后实际运行演示,并附上完整代码。

相关推荐
wanhengidc2 小时前
云手机 流畅稳定 操作简单
服务器·网络·网络协议·安全·智能手机
庞轩px2 小时前
【无标题】
java·开发语言·jvm
Lyyaoo.2 小时前
【JAVA基础面经】JAVA中的泛型
java
自然常数e2 小时前
预处理讲解
java·linux·c语言·前端·visual studio
大数据新鸟2 小时前
设计模式详解——模板方法模式
java·tomcat·模板方法模式
无籽西瓜a2 小时前
【西瓜带你学设计模式 | 第四期 - 抽象工厂模式】抽象工厂模式 —— 定义、核心结构、实战示例、优缺点与适用场景及模式区别
java·后端·设计模式·软件工程·抽象工厂模式
哼?~2 小时前
Linux线程同步
linux
always_TT2 小时前
内存泄漏是什么?如何避免?
android·java·开发语言
智象科技2 小时前
告警自动化赋能运维:意义与价值解析
网络·数据库·人工智能·自动化·告警·一体化运维·ai运维