扫码加入

  • 正文
  • 相关推荐
申请入驻 产业图谱

使用cJosn读写配置文件

03/30 09:51
215
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

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

例如,配置文件config.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 配置文件准备与数据结构定义

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

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

#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 failedn", 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: %sn", 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:%.2fn humiMax:%.2fn brightnessMax:%dn deviceName:%sn",
        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]:%dn", i, g_configData.repeat[i]);
    }

    printf("parse %s okn", CONFIG_FILE);
    return0;
}

1.3 json整体数据的打印

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

// 打印当前全部配置
void show_config(void)
{
    if (!g_root) return;

    char *text = cJSON_Print(g_root);
    printf("n======== current config ========n");
    printf("%sn", 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这一类:

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:%fn", key, node->valueint, node->valuedouble);
            
            // 整数和浮点数,统一按double处理
            dataType = DATA_TYPE_DOUBLE;
            printf("key:%s type is doublen", key);
        }
        elseif (cJSON_IsString(node))
        {
            dataType = DATA_TYPE_STRING;
            printf("key:%s type is stringn", key);
        }
        else
        {
            printf("key:%s type is errn", key);
        }
    } 
    
    return dataType;
}

1.4.2 修改字段的值

根据字段的类型,使用对应的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] = %dn", key, *(int *)(val));
            }
        }
        elseif (DATA_TYPE_DOUBLE == dataType)
        {
            if (cJSON_IsNumber(node))
            {
                cJSON_SetNumberValue(node, *(double *)(val));
                printf("modify [%s] = %.2fn", key, *(double *)(val));
            }
        }
        elseif (DATA_TYPE_STRING == dataType)
        {
            if (cJSON_IsString(node))
            {
                cJSON_SetValuestring(node, (char *)(val));
                printf("modify [%s] = %sn", key, (char *)val);
            }
        }

    } 
    else
    {
        printf("key [%s] not exist or not numnn", 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("下标越界,数组长度: %dn", size);
        return;
    }

    cJSON *item = cJSON_GetArrayItem(arr, index);
    if (item && cJSON_IsNumber(item)) 
    {
        cJSON_SetNumberValue(item, val);
        printf("%s[%d] = %dn", arr_key, index, val);
    }
}

1.5 josn保存到文件

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

// 保存内存中的 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 %sn", CONFIG_FILE);
    return0;
}

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) 
        {
            case1:
            {
                show_config();
                break;
            }

            case2: 
            {
                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);
                    }
                    elseif (DATA_TYPE_DOUBLE == dataType)
                    {
                        double valuedouble = 0.0;
                        scanf("%lf", &valuedouble);
                        update_item_value(key, &valuedouble, DATA_TYPE_DOUBLE);
                    }
                    elseif (DATA_TYPE_STRING == dataType)
                    {
                        char valueStr[256] = {0};
                        scanf("%s", valueStr);
                        update_item_value(key, &valueStr, DATA_TYPE_STRING);
                    }
                }
                

                break;
            }

            case3: 
            {
                int idx, val;
                printf("输入数组下标: ");
                scanf("%d", &idx);
                printf("输入新值: ");
                scanf("%d", &val);
                set_array_item("repeat", idx, val);
                break;
            }

            case4:
            {
                save_json();
                cJSON_Delete(g_root);
                return;
            }
            
            case5:
            {
                cJSON_Delete(g_root);
                printf("已退出,未保存n");
                return;
            }
            default:
                printf("无效选项n");
                break;
        }
    }
}

2 代码测试

2.1 运行查看配置数据

2.2 修改number类型的值

2.3 修改字符串类型的值

2.4 修改数组的值

2.2 保存数据

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

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"

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

typedefenum{
    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 failedn", 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: %sn", 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:%.2fn humiMax:%.2fn brightnessMax:%dn deviceName:%sn",
        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]:%dn", i, g_configData.repeat[i]);
    }

    printf("parse %s okn", CONFIG_FILE);
    return0;
}

// 保存内存中的 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 %sn", CONFIG_FILE);
    return0;
}

// 打印当前全部配置
void show_config(void)
{
    if (!g_root) return;

    char *text = cJSON_Print(g_root);
    printf("n======== current config ========n");
    printf("%sn", 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:%fn", key, node->valueint, node->valuedouble);
            
            // 整数和浮点数,统一按double处理
            dataType = DATA_TYPE_DOUBLE;
            printf("key:%s type is doublen", key);
        }
        elseif (cJSON_IsString(node))
        {
            dataType = DATA_TYPE_STRING;
            printf("key:%s type is stringn", key);
        }
        else
        {
            printf("key:%s type is errn", 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] = %dn", key, *(int *)(val));
            }
        }
        elseif (DATA_TYPE_DOUBLE == dataType)
        {
            if (cJSON_IsNumber(node))
            {
                cJSON_SetNumberValue(node, *(double *)(val));
                printf("modify [%s] = %.2fn", key, *(double *)(val));
            }
        }
        elseif (DATA_TYPE_STRING == dataType)
        {
            if (cJSON_IsString(node))
            {
                cJSON_SetValuestring(node, (char *)(val));
                printf("modify [%s] = %sn", key, (char *)val);
            }
        }

    } 
    else
    {
        printf("key [%s] not exist or not numnn", 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("下标越界,数组长度: %dn", size);
        return;
    }

    cJSON *item = cJSON_GetArrayItem(arr, index);
    if (item && cJSON_IsNumber(item)) 
    {
        cJSON_SetNumberValue(item, val);
        printf("%s[%d] = %dn", 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) 
        {
            case1:
            {
                show_config();
                break;
            }

            case2: 
            {
                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);
                    }
                    elseif (DATA_TYPE_DOUBLE == dataType)
                    {
                        double valuedouble = 0.0;
                        scanf("%lf", &valuedouble);
                        update_item_value(key, &valuedouble, DATA_TYPE_DOUBLE);
                    }
                    elseif (DATA_TYPE_STRING == dataType)
                    {
                        char valueStr[256] = {0};
                        scanf("%s", valueStr);
                        update_item_value(key, &valueStr, DATA_TYPE_STRING);
                    }
                }
                

                break;
            }

            case3: 
            {
                int idx, val;
                printf("输入数组下标: ");
                scanf("%d", &idx);
                printf("输入新值: ");
                scanf("%d", &val);
                set_array_item("repeat", idx, val);
                break;
            }

            case4:
            {
                save_json();
                cJSON_Delete(g_root);
                return;
            }
            
            case5:
            {
                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();
    return0;
}

4 总结

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

相关推荐

登录即可解锁
  • 海量技术文章
  • 设计资源下载
  • 产业链客户资源
  • 写文章/发需求
立即登录