[C/Cpp项目笔记] 流程式C语言项目笔记:cJSON源码解析

源码地址:https://sourceforge.net/projects/cjson/

目录

0.前言

1.cJSON简介

2.项目的文件结构及CJSON结构体定义

2.1 文件结构

2.2 CJSON结构体定义及模型

3.了解项目功能(test.c的学习)

3.1 doit函数

3.2 dofile函数

3.3 create_objects函数

3.4 总结

【实现细节(cJSON.c)】

4.cJSON_Parse:字符串解析函数

4.0 cJSON_New_Item()

4.1 parse_value

4.1.0 parse_array

4.1.1 parse_string 

4.1.2 parse_number

4.1.4. parse_object

4.2 处理字符串不合法的情况

5.cJSON_Delete:释放json架构体内存

6.cJSON_Print : json架构体打印函数

6.0 应用buffer的机制 printbuffer *p

6.0.0 printbuffer 结构体

6.0.1 ensure(printbuffer *p, int needed)

6.0.2 cJSON_strdup(const char* str)

6.0.3 项目中涉及到p的场景

6.2 print_number

6.3 print_string

6.4 print_array(cJSON *item,int depth,int fmt,printbuffer *p)

6.5 print_object

7.简聊 cJSON_Hooks:自定义内存分配

7.1 cJSON_Hooks 定义

7.1 使用cJSON_Hooks

8.其他标准库函数

8.0 memset()

8.1 memcpy()

8.2 strcpy()

8.3 strchr()


0.前言

先修知识:Cpp or C语言。(博主本科学过Cpp,近期完成《Essential C++》阅读)

简介:本项目名为cJSON,其中json是一种数据交换格式,开头的c表示此项目是由c语言进行编写

项目的核心目标:基于 符合json语法规则的字符串 或者 一系列以C编写的json构造语句,将 json的各元素以结构体表示构造多个cJSON结构体之间的联系,为了实现可视化,对cJSON的打印也是其重要内容

使用方法:对照IDE上的代码,按照本博文章节顺序看代码

收获:

  • 内存的分配与释放
  • 结构体的定义与使用
  • 标准化数据包的解析方法(json)
  • 使用指针对情况进行判断并操作的算法架构(str指向输入,str2指向输出,根据str内的格式控制字符将其内容以一定格式复制到str2)
  • 初探buffer,将数据存在buffer内,并预留空间

p.s.此项目为博主接触的第一个C/Cpp项目,因此以大致弄清核心代码为目标进行学习和整理,因为没有对C进行深入学习,势必会有许多细节被忽略,如读者发现有错误或者令你拍案叫绝的精心编码设计,还请不吝赐教(>▽<)

在此感谢友人lx的帮助与心得交流,让我能在一个相对短的时间内完成对此项目的学习

1.cJSON简介

对详细的内容有兴趣请右转其他博客,若想尽快获取足以支撑本项目的基本cjson概念可阅读此节 

json是一种将多种元素组织起来的格式,其中主要考虑的元素之间的关系有两种:

  1. 并列关系,用 [ ] 表示,多个并列的元素在方括号内用 逗号 连接。用 [ ] 表示的内容称作数组。数组内的元素和与数组之间的关系为 child 
    例子:
  2. 键-值 关系,用 { } 表示,键与值的对应关系在花括号中用 冒号 表示。用 { } 表示的内容称作对象
    例子:

 其中所谓元素,可以是 字符串(用 “ ” 括起来)、数字(包括整数、浮点数)、也可以是其他 数组 或者 对象(也就是说允许嵌套)。

2.项目的文件结构及CJSON结构体定义

2.1 文件结构

以推荐的查看/学习顺序列出

  1. cJSON.h:cJSON结构体的定义[重要]、cJSON_Hooks结构体的定义、cJSON Types定义及一堆函数签名
  2. test.c:可以看作main函数,在这里给出了项目的测试案例,调用了大量 cJSON.c 的内容,经由此做入口可窥见整个项目的内容
  3. cJSON.c:项目的核心,重点学习对象
  4. README:内容不少,相当于官方的指导手册,可惜是用英文写的,个人没有使用
  5. LICENSE:完全没啥用

2.2 CJSON结构体定义及模型

typedef struct cJSON {
   struct cJSON *next,*prev;  /* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */
   struct cJSON *child;      /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */

   int type;              /* The type of the item, as above. */

   char *valuestring;       /* The item's string, if type==cJSON_String */
   int valueint;           /* The item's number, if type==cJSON_Number */
   double valuedouble;          /* The item's number, if type==cJSON_Number */

   char *string;           /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */
} cJSON;

struct cJSON定义如上,其成员列表说明:

  • cJSON 指针 next,prev:数组中多个并列的元素之间对象中多个并列的 键-值对之间 用 next 和 prev 链接
  • cJSON 指针 child:
    • 对象是一个元素,对象内部的单个 键-值对 是单个元素,对象到对象内部的第一个 键-值对,由child链接
    • 数组整体是一个元素,由数组到数组内部的首个元素,由child链接

p.s.从上述定义可以感受到,数组内部的成员,各自为一个独立的JSON;一个 键-值对 整体,为一个独立的JSON。

  • type:表示json元素内容的类型,其取值范围为宏定义
    #define cJSON_False 0
    #define cJSON_True 1
    #define cJSON_NULL 2
    #define cJSON_Number 3
    #define cJSON_String 4
    #define cJSON_Array 5
    #define cJSON_Object 6
  • valuestring:仅用于json元素的内容为字符数组的情况,用此成员记录
  • valueint:仅用于json元素的内容为数字的情况,该内容的int形式用此成员记录
  • valuedouble:仅用于json元素的内容为数字的情况,该内容的int形式用此成员记录
  • string:仅用于json元素为 键-值对 的情况,其键值(键值必为字符数组)用string表示。(也就是说其内容由valuestring / valueint+ valuedouble 表示)

模型示例:

【对象】

直接打印:

CJSON架构体的数据结构:

为突出结构的一致性,将没有用到的成员也列出来了,没有使用的变量成员为空,没有使用的指针成员用 / 标记

 【数组】

直接打印:

CJSON架构体的数据结构:

以上两个图均用作示例,现在给出的目的在于理解CJSON元素及CJSON架构体的含义,具体构建方式可通过查看3.3 节所述的代码得到。

3.了解项目功能(test.c的学习)

这里我们首先从main函数出发,了解代码的功能,然后深入一层学习 test.c 前面定义的函数,在这些函数中会对cJSON.c的函数进行调用,用到时我们再深入学习cJSON.c

首先明确 main函数中并非是一个完整的内容,而是按顺序写了三个独立且完整的内容

解析(parse):这里的解析是指传入一个字符串(当然该字符串符合cjson的格式),然后将该字符串中的元素提取出来以cJSON的结构体进行表示,并根据字符串中的格式控制字符( [ ] 、{ }、: 、, )将cJSON结构体关联起来。

json架构体:解析一个完整的 符合json格式的字符串 所得到的内容称之为json架构体,具体实现中,json架构体由多个json struct 构成,每个json struct表示一个元素,不同的 json struct 通过 cJSON类 中的 next prev child 等关联起来。若将元素视为节点,将元素之间的关联视为连接线,那么一个完整的json可看作一个图的结构,因此我将其称之为json架构体。顺带一提,这个名词是博主自作主张的命名。

p.s. 需要注意的是,在test.c中的流程是:字符串 - 解析得到cJSON - 打印cJSON ,而打印cJSON和打印字符串本身貌似没啥区别,但是实际上在具体应用中我们的使用场景为:

  1. 解析字符串得到cJSON,即:字符串 - 解析得到cJSON
  2. 打印cJSON,即:cJSON - 打印cJSON

因此在 test.c 中看它的流程可能有种脱了裤子放p的感觉

内容1:定义了五个字符数组并通过 do_it 函数对该数组进行了解析和打印

	/* a bunch of json: */
	char text1[]="{\n\"name\": \"Jack (\\\"Bee\\\") Nimble\", \n\"format\": {\"type\":       \"rect\", \n\"width\":      1920, \n\"height\":     1080, \n\"interlace\":  false,\"frame rate\": 24\n}\n}";
	char text2[]="[\"Sunday\", \"Monday\", \"Tuesday\", \"Wednesday\", \"Thursday\", \"Friday\", \"Saturday\"]";
	char text3[]="[\n    [32, -1, 0],\n    [1, 0, 0],\n    [0, 0, 1]\n	]\n";
	char text4[]="{\n		\"Image\": {\n			\"Width\":  800,\n			\"Height\": 600,\n			\"Title\":  \"View from 15th Floor\",\n			\"Thumbnail\": {\n				\"Url\":    \"http:/*www.example.com/image/481989943\",\n				\"Height\": 125,\n				\"Width\":  \"100\"\n			},\n			\"IDs\": [116, 943, 234, 38793]\n		}\n	}";
	char text5[]="[\n	 {\n	 \"precision\": \"zip\",\n	 \"Latitude\":  37.7668,\n	 \"Longitude\": -122.3959,\n	 \"Address\":   \"\",\n	 \"City\":      \"SAN FRANCISCO\",\n	 \"State\":     \"CA\",\n	 \"Zip\":       \"94107\",\n	 \"Country\":   \"US\"\n	 },\n	 {\n	 \"precision\": \"zip\",\n	 \"Latitude\":  37.371991,\n	 \"Longitude\": -122.026020,\n	 \"Address\":   \"\",\n	 \"City\":      \"SUNNYVALE\",\n	 \"State\":     \"CA\",\n	 \"Zip\":       \"94085\",\n	 \"Country\":   \"US\"\n	 }\n	 ]";

	/* Process each json textblock by parsing, then rebuilding: */
	doit(text1);
	doit(text2);
	doit(text3);
    doit(text4);
    doit(text5);

内容2:通过 dofile函数 对 tests文件夹下的 test1~5 文件内容,并在dofile中调用 do_it 对文件内容进行了解析和打印

	// 这里的地址需要自己注意一下,他是以test.exr而非test.c作为当前文件进行相对寻址的
    // 我这里使用的是cLION作为IDE,test.exe存放在项目目录下的cmake-build-debug文件夹下
    dofile("../tests/test1");
	dofile("../tests/test2");
	dofile("../tests/test3");
	dofile("../tests/test4");
	dofile("../tests/test5");

内容3:该函数内分别对 多个cjson元素 而非整体进行了定义,通过一系列c语句对cjson结构进行了定义

    create_objects();

3.1 doit函数

void doit(char *text)
{
	char *out;cJSON *json;
	
	json=cJSON_Parse(text);  // 通过char* 构造json
	if (!json) {printf("Error before: [%s]\n",cJSON_GetErrorPtr());}  // 若构造失败
	else
	{
		out=cJSON_Print(json);  // 将json转化成可打印的字符串
		cJSON_Delete(json);     // 释放json所占用的空间
		printf("%s\n", out);    // 打印
		free(out);              // 释放字符串所占用的空间
	}
}

如上所示此函数接受一个字符串数组 text,通过 cJSON_Parse函数 构造 json架构体,在构造成功的前提下将该架构体转换成字符串并打印,并且通过 cJSON_Delete 和 free 函数释放架构体和字符串所占用的空间。

3.2 dofile函数

/* Read a file, parse, render back, etc. */
void dofile(char *filename)  // 从文件中读取json的字符串(未解析)
{
	FILE *f;long len;char *data;
	
	f=fopen(filename,"rb");
	fseek(f,0,SEEK_END);  // 将f偏移到文件末尾
	len=ftell(f);  // 返回f的当前文件位置(即文件大小)
	fseek(f,0,SEEK_SET);  // 将f偏移到文件开头
	data=(char*)malloc(len+1);fread(data,1,len,f);fclose(f);  // 分配内存空间
	doit(data);  // 解析字符串
	free(data);  // 释放空间
}

从文件中读取字符串内容并通过doit函数解析,与内容1相比只是字符串的来源不同,无根本差别

3.3 create_objects函数

此函数比较长,但是其并非一个整体流程而是将多个完整的 json 构建过程写在了一起,主要可分为以下几个部分:

【3.3.1 变量初始化/声明部分】

一些工具性质的指针、用来生成json元素的素材变量(字符串数组、int矩阵、int数组等)

	cJSON *root,*fmt,*img,*thm,*fld;  // 声明了一堆cJSON指针
	char *out;int i;	/* declare a few. */

	/* Our "days of the week" array: */
	const char *strings[7]={"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"};
	/* Our matrix: */
	int numbers[3][3]={
  {0,-1,0},{1,0,0},{0,0,1}};
	/* Our "gallery" item: */
	int ids[4]={116,943,234,38793};
	/* Our array of "records": */
	struct record f
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值