本文主要介绍了如何使用C语言实现Json数据的生成与解析
写在前面
为什么需要用C语言区解析json文件呢?
在项目应用中,需要文件流读写配置文件,实际上完全可以使用纯文本的配置文件代替json格式的配置,但是我个人觉得json更现代化,并且结构清晰。索性就折腾一下。
首先要看有没有现成的轮子,如果有现成的轮子何必自己造呢?搜了一下,果真有,就是纯C语言实现的uJson库。
项目地址:https://github.com/DaveGamble/cJSON
安装或者准备uJson库
由于本身有源码,所以使用uJson库有两种方式:
- 使用编译好的动态库或者静态库
- 将源码导入到自己工程中一起参与编译,cJson.c和cJson.h
第二种方式就不演示了,这里介绍第一种方式,我习惯将这种库统一编译好。
首先,我们先克隆代码仓库
1
| git clone https://github.com/DaveGamble/cJSON.git
|
在项目文件夹下新建build文件夹

这里省略一些细节,编译器编译环境,注意这里的编译器要与最终你项目的编译器统一;
在build文件夹下执行Cmake生成makefile文件。

我这里默认编译器是mingw64,执行make编译,生成cJson动态库。

我们最终需要的是这几个文件,我们发现最终编程出来的是动态库,动态库的特点就是使用的时候不链接的,运行的程序的时候再调用。所以这个dll文件需要放到跟我们exe程序相同目录下
加载到自己的项目中并测试cJson库的使用,尽管在原作者的项目中包含了相关测试的内容,Anyway,我才不管,我只用库!!!
uJson库的使用
我的需求是既需要生成json(改写json),也需要解析json。所以都来一遍吧。
封装Json结构并打印
我们假定一个结构体:
1 2 3 4 5 6 7 8 9 10
| { "APP_IS_VALID": true, "REQUEST_UPDATE": false, "APP_INFO": { "VERSION": "0.00.001", "RELEASE_DATE": "2025-03-16", "SH256": "0012300123000102301203012312326700123001230001023012030123123267" }, "CONFIGURATION_TYPE_LIST": ["High","Normal","Low"] }
|
这个结构体是我在项目中要用的相关配置。展示下demo的目录结构:

老样子,build中存放编译相关文件,记得拷贝cJson.dll到build文件夹,我们再mian中实现测试。只写main,仿佛回到了C语言上机有木有?
这里贴出针对示例的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| #include <stdio.h> #include <stddef.h> #include "cJson/cJson.h" #include <stdlib.h>
void demo1(void) { cJSON *APP_INFO = cJSON_CreateObject(); cJSON_AddItemToObject(APP_INFO, "VERSION", cJSON_CreateString("0.00.001")); cJSON_AddItemToObject(APP_INFO, "RELEASE_DATE", cJSON_CreateString("2025-03-16")); cJSON_AddItemToObject(APP_INFO, "SH256", cJSON_CreateString("0012300123000102301203012312326700123001230001023012030123123267"));
cJSON* CONFIGURATION_TYPE_LIST = cJSON_CreateArray(); cJSON_AddItemToArray(CONFIGURATION_TYPE_LIST, cJSON_CreateString("High")); cJSON_AddItemToArray(CONFIGURATION_TYPE_LIST, cJSON_CreateString("Normal")); cJSON_AddItemToArray(CONFIGURATION_TYPE_LIST, cJSON_CreateString("Low"));
cJSON* GLOBAL_CFG = cJSON_CreateObject(); cJSON_AddItemToObject(GLOBAL_CFG, "APP_IS_VALID", cJSON_CreateBool(0)); cJSON_AddItemToObject(GLOBAL_CFG, "REQUEST_UPDATE", cJSON_CreateBool(0)); cJSON_AddItemToObject(GLOBAL_CFG, "APP_INFO", APP_INFO); cJSON_AddItemToObject(GLOBAL_CFG, "CONFIGURATION_TYPE_LIST", CONFIGURATION_TYPE_LIST);
char *cPrint = cJSON_Print(GLOBAL_CFG); char *cPrintUnformatted = cJSON_PrintUnformatted(GLOBAL_CFG);
printf("cJSON_Print:\n%s\n", cPrint); printf("cJSON_PrintUnformatted:\n%s\n", cPrintUnformatted); free(cPrint); free(cPrintUnformatted); }
int main(void) { demo1(); }
|
然后我们编译运行…

编译通过后直接执行,测试成功!!!

解析Json文件并获取属性
解析时从严谨和安全角度,需要使用结构体中的type类型进行判断,再取数据或者对象;可以使用cJSON_Print函数去获取字符串,也可以使用结构体中的valuestring取值,但要注意的是,使用cJSON_Print函数去获取字符串之后需要free掉获取到的指针,否则会造成内存泄漏!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| #include <string.h> #include <sys/stat.h> #include <stdbool.h>
void demo2() { FILE *file = NULL; file = fopen("../cfg.json", "r"); if (file == NULL) { printf("Open file failed!\n"); return; } struct stat statBuffer; stat("../cfg.json", &statBuffer); int fileSize = statBuffer.st_size;
char *jsonStr = (char *)malloc(sizeof(char) * fileSize + 1); memset(jsonStr, 0, fileSize + 1);
int size = fread(jsonStr, sizeof(char), fileSize, file); if (size == 0) { printf("Read file Failed!\n"); fclose(file); return; } printf("%s\n", jsonStr); fclose(file);
cJSON *root = cJSON_Parse(jsonStr); if (!root) { printf("Error before: [%s]\n", cJSON_GetErrorPtr()); free(jsonStr); return; } free(jsonStr);
cJSON *item = NULL; char *v_str = NULL; double v_double = 0.0; int v_int = 0; bool v_bool = false;
item = cJSON_GetObjectItem(root, "APP_INFO"); if (item != NULL) { if (item->type == cJSON_Object) { cJSON* version = NULL; version = cJSON_GetObjectItem(item, "VERSION"); if (version->type == cJSON_String) { v_str = version->valuestring; printf("VERSION = %s\n", v_str); } } } }
int main(void) { demo2(); }
|
在上面这个示例中,先从cfg.json文件中一次性读取所有的字符信息,然后将字符一次性转换成jsonString然后解析。这里以获取APP的版本号为例:

最终执行可以拿到版本信息。
cJson的使用注意点
由于C语言是强类型语言,任何json中的独立属性对于C实现来说都视为一个独立的对象,然后用指针将这些对象联系起来;具体的话,可以直接阅读cJson.h的函数,都很直观。使用时一定要注意动态内存的释放!
后记
尽管json更现代,但是在实际嵌入式平台,如果没有系统内存管理支撑,不建议用c去搞json数据,动态内存分配着实有点令人担心呢!
其实,不难从我的配置内容看出,这是一个典型的嵌入式Bootloader和App的配置设置,其实我正在寻求一种在通用平台(PC)模拟单片机中程序跳转以及软件升级的机制。