cJSON是一个轻巧的,ANSI-C标准的json解析库,其代码只有1000+行,写得非常漂亮,适合用来学习。本文结合cjson的源码简单分析该json库。学习cJSON之前可以先看一下json的简介。
数据结构定义
json里面的数据有7形式,在cJSON中定义如下:
| /* cJSON Types: */ #define cJSON_False  (1 << 0) #define cJSON_True   (1 << 1) #define cJSON_NULL   (1 << 2) #define cJSON_Number (1 << 3) #define cJSON_String (1 << 4) #define cJSON_Array  (1 << 5) #define cJSON_Object (1 << 6) /* 这两种定义在下文中会有描述 */ #define cJSON_IsReference 256 #define cJSON_StringIsConst 512
 | 
json数据的采用了树的形式存储,其中每一个节点cJSON的定义如下:
| typedef struct cJSON { 	struct cJSON *next,*prev;	 	struct cJSON *child;		 	int type;			 	char *valuestring;		 	int valueint;			 	double valuedouble;		 	char *string;			 } cJSON;
 | 
解析类函数
cJSON定义了许多函数,我把这些函数做了简单的分类,大致可以分为4类:解析类函数,打印类函数,节点操作类函数(节点增删查改),其他函数。首先先来看一下解析类函数。解析类函数中最重要的就是cJSON_Parse,该函数传入一个json字符串,并把他解析成cJSON格式的链表。
| cJSON *cJSON_Parse(const char *value) {return cJSON_ParseWithOpts(value,0,0);}
 | 
该函数调用了cJSON_ParseWithOpts函数,在这个函数中,省略其他细枝末节,最关键的是调用了parse_value函数,其函数定义如下:
| static const char *parse_value(cJSON *item,const char *value,const char **ep) { 	if (!value)						return 0;	 	if (!strncmp(value,"null",4))	{ item->type=cJSON_NULL;  return value+4; } 	if (!strncmp(value,"false",5))	{ item->type=cJSON_False; return value+5; } 	if (!strncmp(value,"true",4))	{ item->type=cJSON_True; item->valueint=1;	return value+4; } 	if (*value=='\"')				{ return parse_string(item,value,ep); } 	if (*value=='-' || (*value>='0' && *value<='9'))	{ return parse_number(item,value); } 	if (*value=='[')				{ return parse_array(item,value,ep); } 	if (*value=='{')				{ return parse_object(item,value,ep); } 	*ep=value;return 0;	 }
 | 
该函数是解析json字符串的核心函数,通过比较字符串的首个字符来判数据类型,并调用相应数据类型的解析函数来解析,比如字符串以"开头,那么可以假设后面的数据类型是字符串类型,调用parse_string函数来解析。当然,这里只是假设后面是字符串类型,如果解析下去发现格式不符,那么就报错返回。由于json数据类型之间是可以相互嵌套的,比如对象类型里面可以嵌套数组类型,数组类型里面又可以嵌套对象类型,因此,解析的时候其实也是一个函数递归调用的过程。
下面来看parse_string解析函数:
| static const char *parse_string(cJSON *item,const char *str,const char **ep) { 	 	while (*end_ptr!='\"' && *end_ptr && ++len) if (*end_ptr++ == '\\') end_ptr++;	 	out=(char*)cJSON_malloc(len+1);	 	if (!out) return 0; 	item->valuestring=out;  	item->type=cJSON_String; 	 	while (ptr < end_ptr) 	{ 		if (*ptr!='\\') *ptr2++=*ptr++; 		else 		{ 			ptr++; 			switch (*ptr) 			{ 				case 'b': *ptr2++='\b';	break; 				case 'f': *ptr2++='\f';	break; 				case 'n': *ptr2++='\n';	break; 				case 'r': *ptr2++='\r';	break; 				case 't': *ptr2++='\t';	break; 				case 'u':	  					uc=parse_hex4(ptr+1);ptr+=4;	 					 				default:  *ptr2++=*ptr; break; 			} 			ptr++; 		} 	} 	 	return ptr; }
 | 
可以看到,整个函数的框架就是按照下图的状态机来解析json字符串的。函数首先计算字符串的长度,即寻找字符串的结尾",并且计算长度的时候跳过转义符\。计算完长度之后为字符串分配相应的内存空间,并把字符逐个拷贝到分配的内存空间中。拷贝的过程中需要处理几个特殊的转义字符以及unicode字符(以\u开头),其中unicode通过parse_hex4函数特殊处理,这个函数就不详细展开了。

parse_number函数用于解析数字,其函数定义如下:
| static const char *parse_number(cJSON *item,const char *num) { 	double n=0,sign=1,scale=0;int subscale=0,signsubscale=1; 	if (*num=='-') sign=-1,num++;			 	if (*num=='0') num++;				 	if (*num>='1' && *num<='9') { 		do 			n=(n*10.0)+(*num++ -'0'); 		while (*num>='0' && *num<='9');		 	} 	if (*num=='.' && num[1]>='0' && num[1]<='9') { 		num++;		 		do	 			n=(n*10.0)+(*num++ -'0'),scale--;  		while (*num>='0' && *num<='9'); 	}						 	if (*num=='e' || *num=='E') {			 		num++; 		if (*num=='+')  			num++;	 		else if (*num=='-')  			signsubscale=-1,num++;		 		while (*num>='0' && *num<='9')  			subscale=(subscale*10)+(*num++ - '0');	 	} 	n=sign*n*pow(10.0,(scale+subscale*signsubscale));	 	 	item->valuedouble=n; 	item->valueint=(int)n; 	item->type=cJSON_Number; 	return num; }
 | 
为了能看清代码,我对源代码的格式稍微做了调整。同样的,对于数字的解析也是对下图状态机的一个实现,对照图看代码部分很清晰。

parse_array函数用于解析数组,其定义如下:
| static const char *parse_array(cJSON *item,const char *value,const char **ep) { 	 	value=skip(parse_value(child,skip(value),ep));	 	if (!value) return 0; 	while (*value==',') 	{ 		cJSON *new_item; 		if (!(new_item=cJSON_New_Item())) return 0; 	 		child->next=new_item;new_item->prev=child;child=new_item; 		value=skip(parse_value(child,skip(value+1),ep)); 		if (!value) return 0;	 	} 	if (*value==']') return value+1;	 	*ep=value;return 0;	 }
 | 
数组是用于存放别的数据的数据类型,因此对于数组的解析,除了解析[和]外,剩下的就是对内部value的解析,所以只需要对数组内每一个元素调用parse_value即可,从这里也体现出,对json的解析其实是一个递归的过程。数组内的每两个元素之间用,隔开,数组内的元素之间是兄弟关系,所以需要指定好他们的前一个节点以及后一个节点的指针。
parse_object函数用于解析对象,其定义如下:
| static const char *parse_object(cJSON *item,const char *value,const char **ep) { 	 	value=skip(parse_string(child,skip(value),ep));  	if (!value) return 0; 	child->string=child->valuestring;child->valuestring=0; 	 	if (*value!=':') {*ep=value;return 0;}	 	 	value=skip(parse_value(child,skip(value+1),ep));	 	if (!value) return 0; 	 	 	while (*value==',') 	{ 		cJSON *new_item; 		if (!(new_item=cJSON_New_Item()))	return 0;  		child->next=new_item;new_item->prev=child;child=new_item; 		value=skip(parse_string(child,skip(value+1),ep)); 		if (!value) return 0; 		child->string=child->valuestring;child->valuestring=0; 		if (*value!=':') {*ep=value;return 0;}	 		value=skip(parse_value(child,skip(value+1),ep));	 		if (!value) return 0; 	} 	 	if (*value=='}') return value+1;	 	*ep=value;return 0;	 }
 | 
对象用于存放多个无序的键值对,其中键是string类型,值可以是任意类型。其解析过程同对数组的解析过程类似,首先解析key,因为key是string类型,调用parse_string获得key的名字,接着解析value,调用parse_value。如果有,则表明还有key-value对,接着循环解析。