1.c语言是如何解析json的
在python里面,我们有json模块可以直接解析json。但是对其中原理,却不是很明白。这个cjson项目,就是用c语言写的解析json的。而且代码量不是很多,很适合用来学习如何解析json数据
2.开始调试
下载的文件有三个文件,分别是cjson.h和cjson.c,test.c。我们先来cjson.h看看
/* cJSON Types: */ #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 #define cJSON_IsReference 256 #define cJSON_StringIsConst 512
这里定义了,json里面的几种类型。分别是bool类型,数字类型,字符串类型,数组类型以及对象类型。
/* The cJSON structure: */ 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;
下面定义了一个cJSON的结构体。这个结构体抽象的是json简单的键值对。一个json是由多个键值对组成的。后面通过链表将这些结构体链接起来。
开始调试看逻辑
这个项目给了一个测试文件test.c。我们想了解一下这个运行逻辑,就先把项目跑起来。然后自己打断点调试
void doit(char *text) { char *out;cJSON *json; json=cJSON_Parse(text); if (!json) {printf("Error before: [%s]\n",cJSON_GetErrorPtr());} else { out=cJSON_Print(json); cJSON_Delete(json); printf("%s\n",out); free(out); } }
首先我们进入这个函数,这个函数里面有个cJSON_Parse(text);函数,看到这个函数名字就知道它是用来解析json字符串的。
/* Default options for cJSON_Parse */ cJSON *cJSON_Parse(const char *value) {return cJSON_ParseWithOpts(value,0,0);}
这个*cJSON_Print()函数的定义就是在这里,在里面又调用了cJSON_ParseWithOpts。
cJSON *cJSON_ParseWithOpts(const char *value,const char **return_parse_end,int require_null_terminated) { const char *end=0; //这个地方调用cJSON_New_Item();申请了一个cjson的结构体。就是抽象出来的键值对 cJSON *c=cJSON_New_Item(); ep=0; //如果没有申请失败,就返回0 if (!c) return 0; /* memory fail */ //这个skip就是获取下一个字符,将前面生成的结构体和value第二个字符传入parse_value进行解析 end=parse_value(c,skip(value)); if (!end){cJSON_Delete(c);return 0;}/* parse failure. ep is set. */ /* if we require null-terminated JSON without appended garbage, skip and then check for a null terminator */ if (require_null_terminated) {end=skip(end);if (*end) {cJSON_Delete(c);ep=end;return 0;}} if (return_parse_end) *return_parse_end=end; return c; }
这个里面的skip函数,就是去掉一些不必要的字符。
static const char *skip(const char *in) {while (in && *in && (unsigned char)*in<=32) in++; return in;}
然后我们进入*parse_value这个函数看看
static const char *parse_value(cJSON *item,const char *value) { if (!value)return 0;/* Fail on null. */ 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); } if (*value=='-' || (*value>='0' && *value<='9')){ return parse_number(item,value); } if (*value=='['){ return parse_array(item,value); } if (*value=='{'){ return parse_object(item,value); } ep=value;return 0;/* failure. */ }
这个地方进行了一个判断,根据传进来的value值进行判断
我们第一次进入的话,肯定是字符‘{’这样我们就进入到解析对象的函数
parse_object
static const char *parse_object(cJSON *item,const char *value) { cJSON *child;//生成一个child结构体 //如果传进来的不不等于{,直接退出 if (*value!='{'){ep=value;return 0;}/* not an object! */ item->type=cJSON_Object;//设置类型为cJSON_Object value=skip(value+1);//这里将指针后移一位,传入skip去掉一些特殊字符 if (*value=='}') return value+1;/* empty array. */ item->child=child=cJSON_New_Item();//这个地方也是申请一个新的cjson的结构体 if (!item->child) return 0;//申请失败,直接返回 value=skip(parse_string(child,skip(value)));//这里直接将value通过parse_string函数,将获取的键值放到child里面 if (!value) return 0;//如果前面的函数执行失败,!value为false,则直接返回0 child->string=child->valuestring;child->valuestring=0;//这个地方将获取的键的值放到child里面 if (*value!=':') {ep=value;return 0;}/* fail! */ //这个地方继续去解析对应的值,放到child里面 value=skip(parse_value(child,skip(value+1)));/* skip any spacing, get the value. */ if (!value) return 0; //value解析到这里,应该就是,这个字符。接下来就是一个循环,重复解析了 while (*value==',') { cJSON *new_item; if (!(new_item=cJSON_New_Item()))return 0; /* memory fail */ child->next=new_item;new_item->prev=child;child=new_item; value=skip(parse_string(child,skip(value+1))); if (!value) return 0; child->string=child->valuestring;child->valuestring=0; if (*value!=':') {ep=value;return 0;}/* fail! */ value=skip(parse_value(child,skip(value+1)));/* skip any spacing, get the value. */ if (!value) return 0; } //当这个value对应的值为}时候,说明当前对象解析结束,应该结束当前对象解析,将指针指向下一个字符,然后返回 if (*value=='}') return value+1;/* end of array */ ep=value;return 0;/* malformed. */ }
通过我们这几步操作其实就已经生成一个链表。接下来就是输出这个链表了。输出函数的话,是在*print_object这个函数里面
static char *print_object(cJSON *item,int depth,int fmt,printbuffer *p) { char **entries=0,**names=0; char *out=0,*ptr,*ret,*str;int len=7,i=0,j; cJSON *child=item->child; int numentries=0,fail=0; size_t tmplen=0; /* Count the number of entries. */ while (child) numentries++,child=child->next; /* Explicitly handle empty object case */ if (!numentries) { if (p) out=ensure(p,fmt?depth+4:3); elseout=(char*)cJSON_malloc(fmt?depth+4:3); if (!out)return 0; ptr=out;*ptr++='{'; if (fmt) {*ptr++='\n';for (i=0;i<depth-1;i++) *ptr++='\t';} *ptr++='}';*ptr++=0; return out; } if (p) { /* Compose the output: */ i=p->offset; len=fmt?2:1;ptr=ensure(p,len+1);if (!ptr) return 0; *ptr++='{';if (fmt) *ptr++='\n';*ptr=0;p->offset+=len; child=item->child;depth++; while (child) { if (fmt) { ptr=ensure(p,depth);if (!ptr) return 0; for (j=0;j<depth;j++) *ptr++='\t'; p->offset+=depth; } print_string_ptr(child->string,p); p->offset=update(p); len=fmt?2:1; ptr=ensure(p,len);if (!ptr) return 0; *ptr++=':';if (fmt) *ptr++='\t'; p->offset+=len; print_value(child,depth,fmt,p); p->offset=update(p); len=(fmt?1:0)+(child->next?1:0); ptr=ensure(p,len+1); if (!ptr) return 0; if (child->next) *ptr++=','; if (fmt) *ptr++='\n';*ptr=0; p->offset+=len; child=child->next; } ptr=ensure(p,fmt?(depth+1):2); if (!ptr) return 0; if (fmt)for (i=0;i<depth-1;i++) *ptr++='\t'; *ptr++='}';*ptr=0; out=(p->buffer)+i; } else { /* Allocate space for the names and the objects */ entries=(char**)cJSON_malloc(numentries*sizeof(char*)); if (!entries) return 0; names=(char**)cJSON_malloc(numentries*sizeof(char*)); if (!names) {cJSON_free(entries);return 0;} memset(entries,0,sizeof(char*)*numentries); memset(names,0,sizeof(char*)*numentries); /* Collect all the results into our arrays: */ child=item->child;depth++;if (fmt) len+=depth; while (child) { names[i]=str=print_string_ptr(child->string,0); entries[i++]=ret=print_value(child,depth,fmt,0); if (str && ret) len+=strlen(ret)+strlen(str)+2+(fmt?2+depth:0); else fail=1; child=child->next; } /* Try to allocate the output string */ if (!fail)out=(char*)cJSON_malloc(len); if (!out) fail=1; /* Handle failure */ if (fail) { for (i=0;i<numentries;i++) {if (names[i]) cJSON_free(names[i]);if (entries[i]) cJSON_free(entries[i]);} cJSON_free(names);cJSON_free(entries); return 0; } /* Compose the output: */ *out='{';ptr=out+1;if (fmt)*ptr++='\n';*ptr=0; for (i=0;i<numentries;i++) { if (fmt) for (j=0;j<depth;j++) *ptr++='\t'; tmplen=strlen(names[i]);memcpy(ptr,names[i],tmplen);ptr+=tmplen; *ptr++=':';if (fmt) *ptr++='\t'; strcpy(ptr,entries[i]);ptr+=strlen(entries[i]); if (i!=numentries-1) *ptr++=','; if (fmt) *ptr++='\n';*ptr=0; cJSON_free(names[i]);cJSON_free(entries[i]); } cJSON_free(names);cJSON_free(entries); if (fmt) for (i=0;i<depth-1;i++) *ptr++='\t'; *ptr++='}';*ptr++=0; } return out; }
这个函数的话,就是遍历这个传进来的链表,根据不同类型,进入不同的输出函数进行输出。输出完成后,还要对空间进行释放。
总结
这个csjon项目的代码宗地来说比较少·,也有注释。理解起来也比较容易。如果对编译原理感兴趣,想用c语言自制一门编程语言,但是直接看书又不大理解,可以先学一下这个项目试试手。当然,我的这个写的不是很详情,像如何解析数组,具体如何输出的等,当然我上面也可能有错误。大家注意甄别。
下载项目的话,自己百度一下就行。这个项目我下载了,上传我的githup了。地址是https://github.com/ddouworld/study_cjson.git