JSON简介
JSON(JavaScript Object Notation, JS 对象简谱) 是一种轻量级的数据交换格式。它基于 ECMAScript (欧洲计算机协会制定的js规范)的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。 易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。
cJSON简介
cJSON是一个仅有一个.h文件,一个.c文件组成的JSON解析器,它是由纯C(ANSI C89)实现的,跨平台性较好。cJSON是采用链表存储的。
源码分析
由于源码细节太多,所以以下代码只展示核心部分,比如部分变量初始化、申请释放内存等操作省略。
skip函数
static const char *skip(const char *in) //#1
{
while (in && *in && (unsigned char)*in<=32) //#2
in++;
return in;
}
- #1:该函数主要作用是跳过字符串前面的空格和“\n”.
- #2:当指针为空、指针内容为空字符、指针内容的ASCII码值小于32时,指针++,32是空格的ASCII码值,“\n”的ACSII码值为10,空字符的ACSII码值为0。
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. */
}
- 该函数是解释器的核心,通过判断输入字符串的首字符,选择调用不同的解释函数进行解释。递归调用该函数就可以完成对JSON格式的解释。
parse_string函数
顾名思义,该函数是解释字符串用的
static const char *parse_string(cJSON *item,const char *str)
{
...
const char *ptr=str+1; //#1
while (*ptr!='\"' && *ptr && ++len) //#2
if (*ptr++ == '\\') ptr++; //#3
out=(char*)cJSON_malloc(len+1); //#4
ptr=str+1;ptr2=out; //#5
while (*ptr!='\"' && *ptr) //#6
{
if (*ptr!='\\') *ptr2++=*ptr++; //#7
else {ptr++;...ptr++;} //#8
}
*ptr2=0; //#9
if (*ptr=='\"') ptr++; //#10
item->valuestring=out; //#11
item->type=cJSON_String; //#12
return ptr;
}
- #1:定义一个指向输入字符串第二个字符的指针,因为字符串的第一个字符是'"'。
- #2~#4:完成对字符串中字符个数的计数,然后分配内存。计数规则:
ptr++
,直到碰到下一个'"'字符就停止。在需要解释的字符中,可能出现以下情况"\"Jack (\\\"Bee\\\") Nimble\"
",中间部分"\\\"
"解释成"\"
", 所以当遇到字符"\\
"时,ptr
指针应该自加两次,"\\\"
"只算成一个长度,"\"Jack (\\\"Bee\\\") Nimble\"
"解释成Jack (\"Bee\") Nimble
,当后续在此显示这个字符串的时候,将在此发生转义,字符串将显示为Jack ("Bee") Nimble
。 - #5: 将
ptr
指向第二个字符。 - #6:挨个遍历字符串内容,将其赋值给
ptr2
。 - #7~#8:特殊情况是当字符串里又出现转义字符数,将对其进行特殊处理,此处代码详细介绍,功能例如,将"
\\\"
"解释成"'
","\\n
"解释成"\n
"。 - #9:由于字符串必须以'\0'结尾,所以要添加一个空字符作为结尾标志。
- #10:返回指向当前解释完的字符串的下一个需要解释的字符的指针。
- #11~#12:将解释完的内容保存到
cJSON
结构体中,解释完毕。
parse_number函数
顾名思义,将字符串中的数字部分提取出来,这有点像C库中的atoi()
函数。
static const char *parse_number(cJSON *item,const char *num)
{
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_object函数
在JSON格式中,有两种数据,一类是对象object
,一类是数组array
, object
以{
开始,以}
结尾,并由若干条目组成,每个条目格式如下title: content
,title
是字符串类型,content
可以是字符串、数字、object
类型,也就是说object
可嵌套。下面是源码:
static const char *parse_object(cJSON *item,const char *value)
{
...
item->type=cJSON_Object; //#1
item->child=child=cJSON_New_Item(); //#2
value=skip(parse_string(child,skip(value))); //#3
child->string=child->valuestring;child->valuestring=0; //#4
if (*value!=':') {ep=value;return 0;} //#5
value=skip(parse_value(child,skip(value+1))); //#6
while (*value==',') //#7
{
child->next=new_item;new_item->prev=child;child=new_item; //#8
... //#9
}
if (*value=='}') return value+1; //#10
}
- #1~#2: 该sJSON结构体的类型设置,并初始化一个
child
子结构体。 - #3~#5:按照
object
的数据格式,第一项应该是字符串格式的title
,所以首先提取字符串,提取完后需要判断下个字符是不是':'
字符。 - #6:由于
content
部分可以是多种格式,所以需要再次调用parse_value()
函数进行递归解释。 - #7~#8:由于一个
object
有多个条目,并且以','
隔开,所以遍历所有条目,并将用cJION->next
将所有条目连接起来。 - #9: 重复执行#3~#6。
- #10:判断
object
是否结束。
parse_array函数
array
以[
开头,以]
结束,并且在内部也可以进行array
嵌套。过程与parse_object()
函数类似。源码如下:
static const char *parse_array(cJSON *item,const char *value)
{
item->type=cJSON_Array; //#1
item->child=child=cJSON_New_Item(); //#2
value=skip(parse_value(child,skip(value))); //#3
while (*value==',') //#4
{
child->next=new_item;new_item->prev=child;child=new_item; //#5
value=skip(parse_value(child,skip(value+1))); //#6
}
if (*value==']') return value+1; //#7
}
- #1~#2:该sJSON结构体的类型设置,并初始化一个
child
子结构体。 - #3:获取第一个元素的内容。
- #4:判断是否存在下一个元素。
- #5~#6:由于一个
array
有多个元素,并且以','
隔开,所以遍历所有条目,并将用cJION->next
将所有条目连接起来。然后进行下个元素的获取。 - #7: 判断是否结束。
实例
例:若要对下列数据进行解释
"{\n\"name\": \"Jack (\\\"Bee\\\") Nimble\",
\n\"format\": {\"type\": \"rect\",
\n\"width\": 1920,
\n\"height\": 1080, \n\"interlace\": false,
\"frame rate\": 24\n}\n}"
函数执行过程
- 检测到第一个
{
,调用parse_object()
函数。 - 解释第一个条目,调用
parse_string()
函数解释title
:\"name\"
。 - 调用
parse_string()
函数解释content
:\"Jack (\\\"Bee\\\") Nimble\"
。 - 下一个条目的解释,调用
parse_string()
函数解释title
:\"format\"
。 - 检测到第二个
{
,调用parse_object()
函数解释其content
。 - 调用
parse_string()
函数解释title
:\"width\"
。 - 调用
parse_nubmer()
函数解释content
:1920
。 - 重复操作直到解释完
24
,检测到第一个}
,至此\"format\"
的内容解释完了。 - 检测到第二个
}
,整个数据解释完毕。
数据结构
该数据分为两个object
对象。第一个object
为:
第二个
object
为:
总结
- 程序中内存分配的函数为
cJSON_malloc()
。其定义为
static void *(*cJSON_malloc)(size_t sz) = malloc;
为了和malloc
区分开来,定义了一个函数指针cJSON_malloc
并让他指向malloc
函数,这样就可以给函数进行重命名,增强代码的可读性。
- 程序中内存分配部分的表示内存变量大小的数据类型为
size_t
,他表示对象大小的上限,也就是增强了移植性。 - 程序中有一个全局静态变量
eq
,他会跟踪数据解析的位置,当解析出错时,可以根据eq
的值找到解析出错的位置,方便调试。