上篇已实现构建节点树函数接口,该篇介绍如何将代码构建的节点树按格式化打印出来。
基本思路
从根节点开始遍历,采用广度优先。
如何广度优先呢?定义一对字符串数组,分别存放名称和数值,这对数组构成键值对。
继续拿上篇例子举例:
输入root节点->判断为cJSON_Object类型->判断有3个子节点->遍历节点。
首先依次遍历 Gear(name)->Music(name)->Call(name) ,依次将节点名称放入数组name0[] ={"\"Gear\"", "\"Music\"", "\"Call\""}
然后再依次遍历Gear(value)->Music(value)->Call(value),依次将节点value放入数组
value0[] = { "[\"P\",\"R\",\"N\",\"D\",\"S\"]",
"{\n\t\t\"song\": \"This is a song title\",\n\t\t\"isPlaying\": false\n\t},",
"{\n\t\t\"name\": null,\n\t\t\"number\": \"13566136676\",\n\t\t\"duration\": 180\n\t}" }
然后将name0与value0进行拼接,最后输出。
其过程中当遍历到Music(value)
name1[] = {"\"song\"", "\isPlaying\""}
value1[] = {"\"This is a song title\"", "false"}
将name1和value1进行拼接得到value0[1]
当遍历到Call(value)
name2[] = {"\"name\"", "\number\"", "\"duration\""}
value2[] = {"null, \"13566136676\"", "180"}
将name2与value2拼接得到value0[2]
先看运行结果:
cJSON.h头文件:
//调用该函数,从根节点开始打印char *cJSON_Print(cJSON *root);//判断节点类型char *print_value(cJSON *item, int depth);/*根据节点类型格式化输出对应的值*/char *print_array(cJSON *item, int depth);char *print_object(cJSON *item, int depth);char *print_string( cJSON *item );char *print_number(cJSON *item);const char *cJSON_strdup(const char *str);//格式化输出字符串,例如将"Gear"输出为"\"Gear\""char *cJSON_formatString(const char *string);
cJSON.c源文件:
char *cJSON_Print(cJSON *root){ return print_value(root, 0);}char *print_value(cJSON *item, int depth){ if (!item) { return NULL; } char *out = NULL;/*有些编译器中会把define理解为int以外的类型来处理,为保证输出为int,统一用按位与的方式(不会影响值)做转换确保最后输出类型都是int*/ switch (item->type & 255) { case cJSON_NULL: out = cJSON_strdup("null"); break; case cJSON_False: out = cJSON_strdup("false"); break; case cJSON_True: out = cJSON_strdup("true"); break; case cJSON_Number: out = print_number(item); break; case cJSON_String: out = print_string(item); break; case cJSON_Array: out = print_array(item, depth); break; case cJSON_Object: out = print_object(item, depth); break; } return out;}char *print_array(cJSON *item, int depth){ if (!item) { return NULL; } char **names = NULL, **entries = NULL;//name/value键值对 char *out = NULL, *ptr; int len = 0; //计算子节点个数 int numEntries = 0; cJSON *child = item->child; while (child) { numEntries++; child = child->next; } //若子节点个数为0 if (!numEntries) { out = cJSON_malloc( 3 ); if(!out){return NULL;} strcpy(out, "[]"); return out; } //子节点个数不为0 entries = (char**)cJSON_malloc(numEntries * sizeof(char*)); if (!entries) { return NULL; } names = (char**)cJSON_malloc(numEntries * sizeof(char*)); if (!names) { cJSON_free(entries); return NULL; } memset(entries, 0, numEntries * sizeof(char*)); memset(names, 0, numEntries * sizeof(char*)); child = item->child; ++depth; for (int i = 0; i < numEntries; i++) { len += depth;//加depth个\t names[i] = cJSON_formatString(child->nameString); len += strlen(names[i]) + 2;//加一个冒号,加一个空格 entries[i] = print_value(child, depth); len += strlen(entries[i]); if (i != numEntries - 1) { len++; }//加个逗号 //len++;//加个换行 child = child->next; } //字符串拼接 len += depth - 1;//给]加depth-1个\t out = cJSON_malloc(len + 3);//[]\0 if(!out){return NULL;} ptr = out; *ptr++ = '[';// *ptr++ = '\n'; for (int i = 0; i < numEntries; i++) { // for (int j = 0; j < depth; j++) { *ptr++ = '\t'; } if (strcmp(names[i], "\"\"")) { strcpy(ptr, names[i]); ptr += strlen(names[i]); *ptr++ = ':'; *ptr++ = '\x20'; } strcpy(ptr, entries[i]); ptr += strlen(entries[i]); if (i != numEntries - 1) { *ptr++ = ','; } // *ptr++ = '\n'; } //for (int j = 0; j < depth - 1; j++) { *ptr++ = '\t'; } *ptr++ = ']'; *ptr++ = '\0'; cJSON_free(names); cJSON_free(entries); return out;}char *print_object(cJSON *item, int depth){ if (!item) { return NULL; } char **names = NULL, **entries = NULL; char *out = NULL, *ptr; int len = 0; //计算子节点个数 int numEntries = 0; cJSON *child = item->child; while (child) { numEntries++; child = child->next; } //若子节点个数为0 if (!numEntries) { out = cJSON_malloc(4 + depth); if(!out){return NULL;} ptr = out; *ptr++ = '{'; *ptr++ = '\n'; for (int i = 0; i < depth; i++) { *ptr++ = '\t'; } *ptr++ = '}'; *ptr++ = '\0'; return out; } //子节点个数不为0 entries = (char**)cJSON_malloc(numEntries * sizeof(char*) ); if (!entries) { return NULL; } names = (char**)cJSON_malloc(numEntries * sizeof(char*) ); if (!names) { cJSON_free(entries); return NULL; } memset(entries, 0, numEntries * sizeof(char*) ); memset(names, 0, numEntries * sizeof(char*) ); child = item->child; ++depth; for (int i = 0; i < numEntries; i++) { len += depth;//加depth个\t names[i] = cJSON_formatString( child->nameString ); len += strlen(names[i])+2;//加一个冒号,加一个空格 entries[i] = print_value(child, depth); len += strlen(entries[i]); if (i != numEntries - 1) { len++; }//加个逗号 len++;//加个换行 child = child->next; } //字符串拼接 len += depth - 1;//给}加depth-1个\t out = cJSON_malloc( len + 4 );//{\n}\0 if(!out){return NULL;} ptr = out; *ptr++ = '{'; *ptr++ = '\n'; for (int i = 0; i < numEntries; i++) { for (int j = 0; j < depth; j++) { *ptr++ = '\t'; } strcpy(ptr, names[i]); ptr += strlen(names[i]); *ptr++ = ':'; *ptr++ = '\x20'; strcpy(ptr, entries[i]); ptr += strlen(entries[i]); if (i != numEntries-1) { *ptr++ = ','; } *ptr++ = '\n'; } for (int j = 0; j < depth - 1; j++) { *ptr++ = '\t'; } *ptr++ = '}'; *ptr++ = '\0'; cJSON_free(names); cJSON_free(entries); return out;}char *print_string(cJSON *item){ if (!item) { return NULL; } return cJSON_formatString(item->valueString);}char *cJSON_formatString(const char *string){ char *out = NULL, *ptr; int flag = 0, len = 0; //若空指针,则输出\"\" if (!string) { out = (char*)cJSON_malloc(3);//\"\"\0 if (!out) { return NULL; } strcpy(out, "\"\""); return out; } //遍历是否为特殊字符 for (ptr = string; *ptr ; ptr++) { flag |= ((*ptr>0 && *ptr<32) || (*ptr == '\"') || (*ptr == '\\')) ? 1 : 0; } //非特殊字符 if (!flag) { ptr = string; len = strlen(string); out = (char*)cJSON_malloc(len + 3);//"xxx"\0 if (!out) { return NULL; } ptr = out; *ptr++ = '\"'; strcpy(ptr, string); ptr += len; *ptr++ = '\"'; *ptr++ = '\0'; return out; } //存在特殊字符 ptr = string; unsigned char token; //计算字符串需要的长度 while ((token = *ptr) && ++len) { /*\b退格 \f换页 \n换行 \r回车 \t水平制表符tab*/ if (strchr("\"\\\b\f\n\r\t", token)) { //要输出特殊字符要做特殊处理,否则特殊字符会被计算机识别成一个字符 //例如"\""要转成"\\\""; "要输出"\n"得转成"\\n"; "\\"要转成"\\\\";"\b"转成"\\b";故字符长度要加1 len++; } else if (token < 32) { //把所有 ASCII 可显示字符以外的统统转义为 Unicode, //例如\a转成\u0007,故字符长度要加5 //这样处理的目的是避免调用不同编码的文件时出现乱码 len += 5; } ptr++; } out = (char *)cJSON_malloc(len + 3); if (!out) return NULL; char *ptr2; ptr2 = out; ptr = string; *ptr2++ = '\"'; while (*ptr) {//非特殊字符 if ((unsigned char)*ptr > 31 && *ptr != '\"' && *ptr != '\\') { *ptr2++ = *ptr++; } else//特殊字符 { *ptr2++ = '\\'; switch (token = *ptr++) { case '\\': *ptr2++ = '\\'; break; case '\"': *ptr2++ = '\"'; break; 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; default: //非法字符,直接输出字符的Unicode sprintf(ptr2, "u%04x", token); ptr2 += 5; break; } } } *ptr2++ = '\"'; *ptr2++ = '\0';//让指针ptr2指向其它区域,防止再次修改到该字符 return out;}char *print_number(cJSON *item){ if (!item) { return NULL; } char *out = NULL, *str; double d = item->valueDouble; if (d == 0)//item中无数据存放 { str = (char *)cJSON_malloc(2); out = str; if (str) strcpy(str, '0'); } /* 1.DBL_EPSILON最小有效数字,数学极限思维极小值 若fabs((item->valueDouble) - (int)(item->valueInt)) <= DBL_EPSILON成立,则判断valueDouble与valueInt相等 2.判断是否溢出 因为int占4字节32位,根据二进制编码的规则,INT_MAX = 2^31-1,INT_MIN= -2^31.C/C++中,所有超过该限值的数,都会出现溢出,出现warning,但是并不会出现error。 如果想表示的整数超过了该限值,可以使用长整型long long 占8字节64位 */ else if (fabs((double)(item->valueInt) - d) <= DBL_EPSILON && d <= INT_MAX && d >= INT_MIN) { // 2^64+1=21个字符,其中一个\0 str = cJSON_malloc(21); out = str; if (str) { sprintf(str, "%d", item->valueInt); } } else { str = (char *)cJSON_malloc(64); out = str; if (str) { //如果小数值非常接近零,且整数部分值特别大,那么就以xxxxx.0方式输出 if (((floor(d) - d) <= DBL_EPSILON) && fabs(d) < 1.0e60) { sprintf(str, "%.0f", d); } //若数值比1.0e-6小或比1.0e9大,则较适合用科学计数法表示 else if (fabs(d)<1.0e-6 || fabs(1.0e9)) { sprintf(str, "%e", d); } //小数形式输出,默认保留6位小数 else { sprintf(str, "%f", d); } } } return out;}