2023年做过一个在C环境下,用来解析.json配置信息的辅助函数族,是用cJSON这个工具来做的一层封装:
一组完整的读Json配置信息的辅助函数_json帮助函数-CSDN博客
这一次进行代码重构时,在AI的帮助下,意识到这样的事:
json的规范里,并未定义路径分隔符,这就是为啥json解析时用到的那个cJSON那个接口会不太好用的原因。然后就有了对他的一个扩展:
1.为cJSON补充路径数列访问能力
//将.json文件整体读入一个json Obj
cJSON *json_read_text_file(const char *filename){
FILE *file = fopen(filename, "r");
if (file == NULL) {
printf("Failed to open the file.\n");
return NULL;
}
fseek(file, 0, SEEK_END);
long file_size = ftell(file);
fseek(file, 0, SEEK_SET);
char *file_content = (char *)malloc(file_size + 1);
fread(file_content, 1, file_size, file);
file_content[file_size] = '\0';
fclose(file);
cJSON *json = cJSON_Parse(file_content);
free(file_content);
if (json == NULL) {
printf("Failed to parse JSON.\n");
free(file_content);
return NULL;
}
return json;
}
//一个扩展:可以直接使用反斜杠路径提示符访问任何一个json中间节点和叶子节点
cJSON* gpJson_GetItemFromPath(cJSON* root, const char* path) {
cJSON* current = root;
char* path_copy = strdup(path); // 复制路径
char* token = strtok(path_copy, "\\"); // 使用 "\" 分割路径
while (token != NULL) {
if (current == NULL) break; // 提前终止
// 检查 token 是否包含数组下标(如 `key[1]`)
char* bracket = strchr(token, '[');
if (bracket != NULL && bracket[1] >= '0' && bracket[1] <= '9') {
// 提取键名(`key` 部分)
char* key = strndup(token, bracket - token);
*bracket = '\0'; // 临时截断字符串
// 提取下标(`1` 部分)
int index = 0;
char* end_bracket = strchr(bracket + 1, ']');
if (end_bracket != NULL) {
*end_bracket = '\0'; // 临时截断
index = atoi(bracket + 1); // 字符串转整数
}
// 获取对象中的键值
current = cJSON_GetObjectItem(current, key);
free(key); // 释放临时键名内存
// 若当前节点是数组且下标有效,则访问数组元素
if (current != NULL && cJSON_IsArray(current) && index >= 0 && index < cJSON_GetArraySize(current)) {
current = cJSON_GetArrayItem(current, index);
} else {
current = NULL; // 无效访问
}
} else {
// 普通键名访问
current = cJSON_GetObjectItem(current, token);
}
token = strtok(NULL, "\\");
}
free(path_copy); // 释放复制的路径
return current;
}
2.用法演示
//得到配置
int i = 0;
const char topic[1024];
printf("sample group idx = %d\n", idxGroup);
fflush(stdout);
//片段1: 得到jsonObj["channels"][3]["source"]["desc"]["ipAddr"], 一个string
snprintf(topic, 1024, "channels[%d]\\source\\desc\\ipAddr", idxGroup);
strncpy(papp->ip, cJSON_GetStringValue(gpJson_GetItemFromPath(jsonCfg, topic)), sizeof(papp->ip));
//片段2: 得到jsonObj["channels"][3]["source"]["desc"]["saps"], 一个integer
snprintf(topic, 1024, "channels[%d]\\source\\desc\\saps", idxGroup);
papp->saps = (int)cJSON_GetNumberValue(gpJson_GetItemFromPath(jsonCfg, topic));
....
//片段3:路径上可以包含任意的array:jsonObj["channels"][3]["source"]["desc"]["oem_ch"][5]
//
snprintf(topic, 1024, "channels[%d]\\source\\desc\\oem_ch[%i]", idxGroup, i);
cJSON* dumb = gpJson_GetItemFromPath(jsonCfg, topic);
if(dumb == NULL){
papp->ch_src_list[i] = -1;
needCheck = 0;
continue;
}
else{
papp->ch_src_list[i] = (int)cJSON_GetNumberValue(dumb);
printf("% 2d", papp->ch_src_list[i]);
}
3.结论及扩展
3.1 现在辅助函数只有两个函数。
1.读取.json文件;
2.然后就是解析全路径key反斜杠字符串。
3.2 如果使用C++语法,其实完全可以构造出一个支持:
jsonObj["channels"][3]["source"]["desc"]["saps"],这种语法规则的json读写类。
3.3 所有的终端叶子节点的json格式到char *, float之类的数据对象的转换,可以完全用cJSON本身的IO函数处理。不必画蛇添足,做出一堆比如
gpJson_get_string_item()
gpJson_get_float_item()一类的东西。
3.4 cJSON似乎没有处理default值,感觉这个地方可用宏来解决,更简单。仿照示例3.