Contents

cJSON代码阅读(parse)部分

Contents
1
2
3
4
static const char *skip(const char *in) {
  while (in && *in && (unsigned char)*in<=32) in++;
  return in;
}

跳过空白字符。空白字符即ASCII小于等于32的字符。(我还特意查了ascii的表…)。这里我可能会用isspace(掩面逃…)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
static const char *parse_value(cJSON *item,const char *value)
{
    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); }
    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;
}

这里是parser的核心。判断该读入的元素类型。null,true,false这三个简单类型可以直接处理。其他的分别交给parse_number,parse_array和parse_object处理。ep是这里用于错误处理的指针,当出错的时候,ep指针里保存的就是当前出现错误的位置。

首先是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
static const unsigned char firstByteMark[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC };
static 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;}

    while (*ptr!='\"' && *ptr && ++len) if (*ptr++ == '\\') ptr++;

    out=(char*)cJSON_malloc(len+1);
    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':
                    uc=parse_hex4(ptr+1);ptr+=4;

                    if ((uc>=0xDC00 && uc<=0xDFFF) || uc==0)    break;

                    if (uc>=0xD800 && uc<=0xDBFF)
                    {
                        if (ptr[1]!='\\' || ptr[2]!='u')    break;
                        uc2=parse_hex4(ptr+3);ptr+=6;
                        if (uc2<0xDC00 || uc2>0xDFFF)       break;
                        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;
}

首先是遍历了一次整个string,统计出一共的字符个数,存在了len里,中间遇到转义的部分跳过了后面的字符。个数统计好之后,申请内存。这里的cJSON_malloc就是原本的malloc。在这个函数的定义出可以找到:

1
static void *(*cJSON_malloc)(size_t sz) = malloc;

重新从头遍历整个string,不转义的部分是不用处理的。需要特殊处理的就是转义的部分,其实这部分也只有utf-16到utf-8的编码转换问题。(这里的magic number实在是太多了,我还没不知道utf-16和utf-8的编码方式。实在是看不懂了。)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
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;
}

这里数字的处理就简单很多了。 sign表示有没有负号,如果有的话sign是-1,否则sign为1。
去掉前导0之后,读入每位存在n里。 发现有".",并且下一位是数字的话,就说明这是一个小数,开始读入小数点之后的部分。这里用scale来表示当前的数的小数点应该向左移动几位。(这样是不是也同样保证了精度呢?) 读入E,科学计数法表示。处理方式和前面一样。 最后计算出n就可以了。这里把浮点数和整数放在一起处理了。只要在最后valueint只取n的整数部分就可以了。(我自己尝试实现的时候,把整数和小数分开读的,在存类型的时候也尝试把他们分开了。)这种方式需要在输出的时候做一个特殊处理,判断一下valuedouble和valueint之间的差,如果小于给定的eps那就认为这个数字是一个整数,否则认为是小数。

 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
static const char *parse_array(cJSON *item,const char *value)
{
    cJSON *child;
    if (*value!='[')    {ep=value;return 0;}

    item->type=cJSON_Array;
    value=skip(value+1);
    if (*value==']') return value+1;

    item->child=child=cJSON_New_Item();
    if (!item->child) return 0;
    value=skip(parse_value(child,skip(value)));
    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)));
        if (!value) return 0;
    }

    if (*value==']') return value+1;
    ep=value;return 0;
}

接下来是关于数组的处理。和其他时候一样,先判断起始字符合法性。然后重复: 1. 新建cJSON对象
2. 读掉空白字符
3. 读入这个对象
4. 读掉空白字符
5. 判断这个字符是否是“,“。如果是,转到1,如果不是判断结尾字符合法性,退出。
数组里面的元素,是用了一个双向链表实现的。具体的定义在cJSON这个结构体的定义处给出了。

 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
static const char *parse_object(cJSON *item,const char *value)
{
    cJSON *child;
    if (*value!='{')    {ep=value;return 0;}

    item->type=cJSON_Object;
    value=skip(value+1);
    if (*value=='}') return value+1;

    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;}
    value=skip(parse_value(child,skip(value+1)));
    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)));
        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)));
        if (!value) return 0;
    }

    if (*value=='}') return value+1;
    ep=value;return 0;
}

最后有关于parse的就是关于object对象的parse了。常规的判断其实字符,读掉空白字符。这里先判断了一次结束字符来看是不是空的。 因为一旦有了元素之后,在判断下一个元素是否存在的时候,判断的条件就变成了“,”。这部分在数组中有一样的处理。 这里整体的过程其实和数组的区别只在读入单个元素的方法上: 先读入一个string,读冒号“:”,读value。然后每个child的属性里的string部分保存了这个value的名字。(这里这个保存方式,是不是在get的时候的效率会出现问题呢。感觉这里如果多处理一点,对string串做一个hash可能效果会好一点。)

这样parse细节的部分就都看完了。cJSON_Parse函数是对cJSON_ParseWithOpts的一个封装。cJSON_ParseWithOpts函数处理了读入一个value之外的就是判断了是否需要null作为结束,和返回值存到哪里的问题。代码也很简单:

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

    end=parse_value(c,skip(value));
    if (!end)   {cJSON_Delete(c);return 0;}

    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;
}

Related Content