我是靠谱客的博主 细心黑猫,这篇文章主要介绍cJSON源码解析,现在分享给大家,希望可以做个参考。


   cJSONC语言中的一个JSON编解码器,非常轻量级,C开源代码汇总,代码量少,不到一千行,代码的可读性高,很适合作为 C 语言项目进行学习。
   对于 json 格式编码与解码,其实就是类似于一个解释器,主要的原理是运用递归。
  首先看下 cJSON.h 中定义的 cJSON数据结构,

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
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;

  不管是数据类型、字符串类型还是对象类型都使用这个结构体,类型的信息通过type 来标记, cJSON 共定义了 7 中类型。

复制代码
1
2
3
4
5
6
7
8
9
/* 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 //array 类型 #define cJSON_Object 6 //对象类型

   如果是对象或者数组,采用的是双向链表来实现,链表中的每个节点表示数组中的一个元素或者对象中的一个字段。其中 child 表示头结点,nextprev 分别表示下一个节点和前一个节点。valuestringvalueintvaluedouble分别表示字符串、整数、浮点数的字面量。string 表示对象中的某一字段的名称,比如有这样的一个json 字符串,其中的 age 就用 string来表示。

复制代码
1
2
{'age' : 20}

   cJSON 的 API 使用起来很简单,text 表示你需要解析的文件内容,注意释放内存,以免泄露。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
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("%sn",out); free(out); }

1、JSON的解析

   进行解析分析前,我们先了解下 JSON 文件的格式。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
Some JSON: { "name": "Jack ("Bee") Nimble", "format": { "type": "rect", "width": 1920, "height": 1080, "interlace": false, "frame rate": 24 } }

   JSON 的字符串的解析主要是通过 cJSON_Parse 函数来完成的。

复制代码
1
2
3
cJSON *cJSON_Parse(const char *value) {return cJSON_ParseWithOpts(value,0,0);}
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
cJSON *cJSON_ParseWithOpts(const char *value,const char **return_parse_end,int require_null_terminated) { const char *end=0; cJSON *c=cJSON_New_Item(); ep=0; if (!c) return 0; /* memory fail */ 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; }
  • 第一步:先调用 cJSON_New_Item函数 创建一个 cJSON 节点,函数就是为 节点分配内存,并将对应的内存初始化为 0;
复制代码
1
2
3
4
5
6
7
static cJSON *cJSON_New_Item(void) { cJSON* node = (cJSON*)cJSON_malloc(sizeof(cJSON)); if (node) memset(node,0,sizeof(cJSON)); return node; }
  • 调用 parse_value 函数解析文件,该函数是核心部分,在解析前,先skip(value) ,这里的意思就是将字符串前面的空格去掉;
复制代码
1
2
3
4
5
6
7
static const char *skip(const char *in){ while (in && *in && (unsigned char)*in<=32) in++; return in; }
  • 最后一步中,函数的参数中提供的参数 require_null_terminated 是为了确认 JSON 字符串必须以'' 字符结尾。若参数提供了 require_null_terminated ,表示将 返回 JSON 字符串解析完后剩余的部分。

2、 parse_value 函数

复制代码
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
static const char *parse_value(cJSON *item,const char *value) { if (!value) return 0; /* Fail on null. */ //如果value为 `null` ,直接将type 标记为 `cJSON_NULL` if (!strncmp(value,"null",4)) { item->type=cJSON_NULL; return value+4; } //如果value的值为 false ,则将type 标记为 cJSON_False 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; } //如果value以 '"' 开头,表示这是一个字符串 if (*value=='"') { return parse_string(item,value); } //如果 value 以 '0-9' 开头,表示是数字 if (*value=='-' || (*value>='0' && *value<='9')) { return parse_number(item,value); } //如果 value 以 '[' 开头, 表示是array if (*value=='[') { return parse_array(item,value); } //如果 value 以 '{' 开头 , 表示是对象 if (*value=='{') { return parse_object(item,value); } ep=value; return 0; /* failure. */ }
  • 函数 parse_string,这个函数中需要注意的是遇到转移字符的处理;
复制代码
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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
tatic const char *parse_string(cJSON *item,const char *str) { const char *ptr=str+1; char *ptr2; char *out; int len=0; unsigned uc,uc2; if (*str!='"') { //重复确保是一个字符串 ep=str; return 0; } /* not a string! */ while (*ptr!='"' && *ptr && ++len) //跳过注释行 if (*ptr++ == '\') ptr++; /* Skip escaped quotes. */ //分配内存,以保存解析之后的结果 out=(char*)cJSON_malloc(len+1); /* This is how long we need for the string, roughly. */ if (!out) return 0; ptr=str+1; ptr2=out; while (*ptr!='"' && *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': /* transcode utf16 to utf8. */ uc=parse_hex4(ptr+1); ptr+=4; /* get the unicode char. */ if ((uc>=0xDC00 && uc<=0xDFFF) || uc==0) break; /* check for invalid. */ if (uc>=0xD800 && uc<=0xDBFF) /* UTF16 surrogate pairs. */ { if (ptr[1]!='\' || ptr[2]!='u') break; /* missing second-half of surrogate. */ uc2=parse_hex4(ptr+3); ptr+=6; if (uc2<0xDC00 || uc2>0xDFFF) break; /* invalid second-half of surrogate. */ uc=0x10000 + (((uc&0x3FF)<<10) | (uc2&0x3FF)); } len=4; if (uc<0x80) len=1; else if (uc<0x800) len=2; else if (uc<0x10000) len=3; ptr2+=len; switch (len) { case 4: *--ptr2 =((uc | 0x80) & 0xBF); uc >>= 6; case 3: *--ptr2 =((uc | 0x80) & 0xBF); uc >>= 6; case 2: *--ptr2 =((uc | 0x80) & 0xBF); uc >>= 6; case 1: *--ptr2 =(uc | firstByteMark[len]); } ptr2+=len; break; default: *ptr2++=*ptr; break; } ptr++; } } *ptr2=0; if (*ptr=='"') ptr++; item->valuestring=out; item->type=cJSON_String; return ptr; }
  • 函数 parse_number,解析数字很简单,注意符号、小数、指数就差不多了;
复制代码
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
tatic 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++; /* Has sign? */ if (*num=='0') //直接跳过数字前面的无效的 0 num++; /* is zero */ if (*num>='1' && *num<='9') do n=(n*10.0)+(*num++ -'0'); while (*num>='0' && *num<='9'); /* Number? */ if (*num=='.' && num[1]>='0' && num[1]<='9') { num++; do n=(n*10.0)+(*num++ -'0'),scale--; while (*num>='0' && *num<='9');} /* Fractional part? */ if (*num=='e' || *num=='E') /* Exponent? */ { num++; if (*num=='+') num++; else if (*num=='-') signsubscale=-1,num++; /* With sign? */ while (*num>='0' && *num<='9') subscale=(subscale*10)+(*num++ - '0'); /* Number? */ } n=sign*n*pow(10.0,(scale+subscale*signsubscale)); /* number = +/- number.fraction * 10^+/- exponent */ item->valuedouble=n; item->valueint=(int)n; item->type=cJSON_Number; return num; }
  • 函数 parse_array,解析数组的基本思想是对数组中的每一个元素递归调用 parse_value,再将元素连接形成一个链表;
复制代码
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
static const char *parse_array(cJSON *item,const char *value) { cJSON *child; if (*value!='[') { ep=value; return 0;} /* not an array! */ item->type=cJSON_Array; value=skip(value+1);//跳过空白 if (*value==']') return value+1; /* empty array. */ item->child=child=cJSON_New_Item();//每一个数组元素对应一个节点 if (!item->child) return 0; /* memory fail */ value=skip(parse_value(child,skip(value))); /* skip any spacing, get the value. */ if (!value) return 0; //如果元素后面还有',' 表示数组中还有元素,对剩下的元素继续进行上面的操作,构成链表 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_value(child,skip(value+1))); if (!value) return 0; /* memory fail */ } if (*value==']') return value+1; /* end of array */ ep=value; return 0; /* malformed. */ }
  • 函数 parse_object
复制代码
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
static const char *parse_object(cJSON *item,const char *value) { cJSON *child; if (*value!='{') {ep=value;return 0;} /* not an object! */ item->type=cJSON_Object; value=skip(value+1); if (*value=='}') return value+1; /* empty array. */ item->child=child=cJSON_New_Item(); if (!item->child) return 0; value=skip(parse_string(child,skip(value))); 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; 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; } if (*value=='}') return value+1; /* end of array */ ep=value;return 0; /* malformed. */ }

  客户端代码解析完之后需要释放内存,cJSON_Delete

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void cJSON_Delete(cJSON *c) { cJSON *next; while (c) { next=c->next; if (!(c->type&cJSON_IsReference) && c->child) cJSON_Delete(c->child); if (!(c->type&cJSON_IsReference) && c->valuestring) cJSON_free(c->valuestring); if (!(c->type&cJSON_StringIsConst) && c->string) cJSON_free(c->string); cJSON_free(c); c=next; } }

最后

以上就是细心黑猫最近收集整理的关于cJSON源码解析的全部内容,更多相关cJSON源码解析内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(57)

评论列表共有 0 条评论

立即
投稿
返回
顶部