本文主要介绍了如何使用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);

// 记得使用cJSON_Print 和 cJSON_PrintUnformatted返回来的字符指针需要free掉内存!
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() {
/* open json file*/
FILE *file = NULL;
file = fopen("../cfg.json", "r");
if (file == NULL) {
printf("Open file failed!\n");
return;
}
/* fetch file's size */
struct stat statBuffer;
stat("../cfg.json", &statBuffer);
int fileSize = statBuffer.st_size;

/* malloc memory*/
char *jsonStr = (char *)malloc(sizeof(char) * fileSize + 1);
memset(jsonStr, 0, fileSize + 1);


/* read string */
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);

/* parser string to jsonString*/
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;

/* 获取APP_INFO对象 */
item = cJSON_GetObjectItem(root, "APP_INFO");
if (item != NULL) {
/* 判断是否是新的json对象 */
if (item->type == cJSON_Object) {
cJSON* version = NULL;
/* 获取VERSION对象 */
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)模拟单片机中程序跳转以及软件升级的机制。