天天看点

cjson学习篇三:cJSON数据结构和设计思想

说明:

  本文章旨在总结备份、方便以后查询,由于是个人总结,如有不对,欢迎指正;另外,内容大部分来自网络、书籍、和各类手册,如若侵权请告知,马上删帖致歉。

  QQ 群 号:513683159 【相互学习】

内容来源:

  CSDN-cJSON使用详细教程 | 一个轻量级C语言JSON解析器

  JSON的简单介绍以及C语言的JSON库使用

  JSON的简单介绍&cJSON库使用(一)

cJSON数据结构

typedef struct cJSON
{
    struct cJSON *next;	   		 //遍历数组或对象链的后向链表指针
    struct cJSON *prev;	   		 //遍历数组或对象链的前向链表指针
    struct cJSON *child;		 //数组或对象链的孩子节点
    
    int type;					 //key的类型

    char *valuestring;			 //字符串值	
    int valueint;				 //整数值	
    double valuedouble;			 //浮点数值	

    char *string;				 //key的名字
} cJSON;
           

  从以上代码可看出,cJSON设计思想是:链表。

  数据指向:

    

next

指向下一节点,

prev

指向上一节点,

child

指向子节点(故:对象/数组是可以嵌套,指向新链表)

  数据存储:

    

string

表示key的名称,

type

表示key的类型,

valuestring、valueint、valuedouble

则表示对应

type

类型的变量。

  PS:

    一个结点表示 数组的一个元素/对象的一个字段。

cJSON 类型

  不管是数值类型、字符串类型或者对象类型等都使用该结构体,类型信息通过标识符 type来进行判断,cJSON总共定义了7种类型:

#define cJSON_Invalid (0)		//表示一个无效的不包含任何值的项。如果将项设置为所有零字节,则自动具有此类型。
#define cJSON_False  (1 << 0)	//表示一个错误的布尔值。您还可以使用cJSON_IsBool检查布尔值。
#define cJSON_True   (1 << 1)	//表示一个正确的布尔值。您还可以使用cJSON_IsBool检查布尔值。
#define cJSON_NULL   (1 << 2)	//表示一个空值。
#define cJSON_Number (1 << 3)	//表示一个数字值。该值在valuedouble和valueint中以double形式存储。如果数值超出了整数的范围,则valueint使用INT_MAX或INT_MIN。
#define cJSON_String (1 << 4)	//表示一个字符串值。它以零终止字符串的形式存储在valuestring中。
#define cJSON_Array  (1 << 5)	//表示一个数组值。这是通过将child指向表示数组中值的cJSON项链表来实现的。元素使用next和prev链接在一起,其中第一个元素是prev。next == NULL和最后一个元素next == NULL。
#define cJSON_Object (1 << 6)	//表示一个对象值。对象的存储方式与数组相同,唯一的区别是对象中的项将它们的键存储为string。
#define cJSON_Raw    (1 << 7) 	//表示任何类型的JSON,它存储在valuestring中以零结尾的字符数组中。例如,可以使用它来避免反复打印相同的静态JSON以节省性能。cJSON在解析时永远不会创建这种类型。还要注意,cJSON不检查它是否是有效的JSON。
另外还有以下两个标志:
cJSON_IsReference:指定子元素所指向的项和/或valuestring不属于这个项,它只是一个引用。所以cJSON_Delete和其他函数将只释放这个项,而不是它的子元素/valuestring。
cJSON_StringIsConst:这意味着字符串指向常量字符串。这意味着cJSON_Delete和其他函数不会尝试释放字符串。
           

一、构建cJSON

  对于每个值类型,都有一个

cJSON_Create xxx

可用于创建该类型项的函数。所有这些都将分配一个

cJSON

结构,稍后可以用

cJSON_Delete()

删除它。请注意,您必须在某些时候删除它们,否则将导致内存泄漏。

重要提示:

  如果你已经添加了一个项到一个数组或一个对象,你不能删除它与

cJSON_Delete

。将它添加到数组或对象中会转移它的所有权,这样当数组或对象被删除时,它也会被删除。您还可以使用

cJSON_SetValuestring()

来更改

cJSON_String()

valuestring

,而不需要手动释放前面的

valuestring

(1)创建:

①创建任何类型的JSON (cJSON_CreateRaw)

②创建对象的JSON

1.创建:

  

cJSON_CreateObject()

创建一个空对象。

  

cJSON_CreateObjectReference()

创建一个不“拥有”其内容的对象,因此它的内容不会被cJSON_Delete删除。

2.添加项:

  

cJSON_AddItemToObject()

向对象添加项.

  

cJSON_AddItemToObjectCS()

向名称为常量或引用(项目的

key

cJSON

结构中的字符串)的对象添加一个项目,这样它就不会被

cJSON_Delete()

释放。

  

cJSON_AddItemReferenceToArray()

,可添加一个元素作为对另一个对象、数组或字符串的引用。这意味着

cJSON_Delete()

不会删除该项目的子属性或

valuestring

属性,因此如果它们已经在其他地方使用,则不会发生双重释放。

3.取出项:

  

cJSON_DetachItemFromObjectCaseSensitive()

从一个对象中取出一个项,会返回分离的项,所以一定要把它分配给一个指针,否则你会有一个内存泄漏。

4.删除项:

  

cJSON_DeleteItemFromObjectCaseSensitive()

删除项目.工作方式类似于

cJSON_DetachItemFromObjectCaseSensitive()

,然后是

cJSON_Delete()

  

cJSON_ReplaceItemInObjectCaseSensitive()

cJSON_ReplaceItemViaPointer()

在适当的位置替换对象中的项.若失败,

cJSON_ReplaceItemViaPointer()

将返回0。这在内部所做的是分离旧的项,删除它并在其位置插入新项。

5.获取大小:

  

cJSON_GetArraySize()

获得对象的大小.(这样可以工作,因为在内部对象被存储为数组。)

6.访问项:

  

cJSON_GetObjectItemCaseSensitive()

访问一个对象中的一个项

7.遍历:

  要遍历一个对象,可以使用

cJSON_ArrayForEach

宏,方法与使用数组相同。

8.其他:

  

cJSON

还提供了方便的帮助函数,可以快速创建新项并将其添加到对象中,比如

cJSON_AddNullToObject()

。它们返回一个指向新项的指针,如果失败则返回

NULL

③创建数组的JSON

1.创建:

  

cJSON_CreateArray()

创建一个空数组。

  

cJSON_CreateArrayReference()

创建一个不“拥有”其内容的数组,所以它的内容不会被

cJSON_Delete()

删除。

2.添加项:

  

cJSON_AddItemToArray()

将项添加到数组中,将项追加到末尾。

  

cJSON_AddItemReferenceToArray()

可将一个元素添加为另一个项、数组或字符串的引用。这意味着

cJSON_Delete

将不会删除那些项的子属性或

valuestring

属性,因此,如果它们已经在其他地方使用了,就不会发生重复释放。

  

cJSON_InsertItemInArray()

,中间插入项,它将在给定的基于0的索引处插入一个项,并将所有现有项向右移动。

3.取出项:

  

cJSON_DetachItemFromArray()

,从一个给定索引的数组中取出一个项目并继续使用它,它将返回分离的项目,所以一定要将它分配给一个指针,否则您将有内存泄漏。

4.删除项:

  

cJSON_DeleteItemFromArray()

删除项目。它的工作原理类似于

cJSON_DetachItemFromArray()

,但是通过

cJSON_Delete

删除分离的项目。

5.替换项:

  

cJSON_ReplaceItemInArray()

用索引/

cJSON_ReplaceItemViaPointer()

给定元素指针,在适当的位置替换数组中的项.若

cJSON_ReplaceItemViaPointer()

失败,它将返回0。这在内部做的是分离旧项、删除它并在其位置插入新项。

6.获取大小:

  

cJSON_GetArraySize()

获得数组的大小。使用

cJSON_GetArrayItem()

获取给定索引处的元素。

7.遍历:

  因为数组存储为一个链表,通过迭代索引效率低下(O (n²)),所以你可以使用

cJSON_ArrayForEach

宏遍历一个数组在O (n)时间复杂度。

④创建基础类型的JSON

CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean);
CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num);
CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string);
           

(2)删除

释放cJSON

二、解析cJSON

①解析cJSON

CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value);
CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length);
           

  

cJSON_Parse()

:对给定以零结尾的字符串中的一些JSON进行解析。

  

cJSON_ParseWithLength()

:给定一个字符串中的一些JSON(无论是否终止为0)进行解析。

1.空间分配:

  将解析JSON并分配一个表示它的cJSON项树。一旦它返回,将完全负责在与

cJSON_Delete()

一起使用后对它进行释放。分配器默认是

malloc

free

,但是可以使用

cJSON_InitHooks()

(全局)更改。

2.发生错误:

  

cJSON_GetErrorPtr()

:访问指向输入字符串中错误位置的指针。

  注意:这可能会在多线程场景中产生竞争条件,在这种情况下,最好使用

cJSON_ParseWithOpts()

return_parse_end

。默认情况下,解析后的JSON之后的输入字符串中的字符不会被视为错误。

3.更多选项

  若想要更多的选项,使用

cJSON_ParseWithOpts()

return_parse_end

返回一个指针,指向输入字符串中的JSON结尾或错误发生的位置(从而以线程安全的方式替换

cJSON_GetErrorPtr()

require_null_ended

,如果设置为1,那么如果输入字符串包含

JSON

之后的数据,则会导致错误。

  若你想要更多的设置缓冲区长度的选项,可以使用

cJSON_ParseWithLengthOpts()

②输出JSON

  

cJSON_Print

()给定一个

cJSON

项树,将它们打印为字符串(将使用空白来打印格式)

它将分配一个字符串并将树的

JSON

表示形式打印到其中。一旦它返回,您就完全有责任在与分配器一起使用后重新分配它。(通常是免费的,取决于

cJSON_InitHooks

设置了什么)。

  

cJSON_PrintUnformatted ()

打印没有格式,则可使用该函数。

  

cJSON_PrintBuffered ()

:若对结果字符串的大小有一个大致的概念,则可使用该函数。

  

cJSON_PrintPreallocated()

避免这些动态缓冲区分配。它接受一个缓冲区的指针打印到它的长度。如果达到该长度,打印将失败并返回0。如果成功,则返回1。注意,您应该提供比实际需要更多的5个字节,因为cJSON在估计所提供的内存是否足够时不是100%准确的。

  

③其他

1.从object中获取item “string”(根据键找json结点)

2.判断是否有key是string的项 如果有返回1 否则返回0

3 . 返回数组(或对象)中的项数

4.从数组“array”中检索项目编号“index”(根据数组下标index取array数组结点的第index个成员 返回该成员节点)

5.遍历数组

实验:

实验一:构建json,并将其保存到文件中。

创建文件:

creat_json.c

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "cJSON.h"
#define CFGFILE "creat_json.json"  

//构建以下json
// {
// 	"name":"王小二",						 //字符串
// 	"age":18,						       //数值
// 	"favorite_food":["dumplings","ribs"],	              //数组
// 	"has_girlfriend":false,					//布尔值
// 	"car":null,							//null
// 	"address":							//对象
// 	{
// 		"country":"China",
// 		"city":"fuzhou",
// 		"postcode":350000
// 	}
// }
char *buf =  NULL;
void test()
{
       cJSON *object = cJSON_CreateObject();                   //创建一个空对象
 
       cJSON *name =  cJSON_CreateString("王小二");             //创建基础类:string,并添加到对象中  
       cJSON_AddItemToObject(object, "name", name);

       cJSON *age =  cJSON_CreateNumber(18);                   //创建基础类:number,并添加到对象中  
       cJSON_AddItemToObject(object, "age", age);

       cJSON *array =  cJSON_CreateArray();                    //创建数组:array ,并添加到对象中  
       cJSON_AddItemToObject(object, "favorite_food", array);  
       cJSON *dumplings =  cJSON_CreateString("dumplings");    //创建基础类:string ,并添加到数组中    
       cJSON_AddItemToArray(array, dumplings);                 
       cJSON *ribs =  cJSON_CreateString("ribs");              //创建基础类:string ,并添加到数组中              
       cJSON_AddItemToArray(array, ribs);

       cJSON *has_girlfriend =  cJSON_CreateFalse();           //创建基础类:false,并添加到对象中  
       cJSON_AddItemToObject(object, "has_girlfriend", has_girlfriend);

       cJSON *car = cJSON_CreateNull();                        //创建基础类:null,并添加到对象中 
       cJSON_AddItemToObject(object, "car", car);

       cJSON *address =cJSON_CreateObject(); 
       cJSON_AddItemToObject(object, "address", address);

       cJSON *country =  cJSON_CreateString("China");             //创建基础类:string,并添加到对象中  
       cJSON_AddItemToObject(address, "country", country);
       
       cJSON *city =  cJSON_CreateString("fuzhou");             //创建基础类:string,并添加到对象中  
       cJSON_AddItemToObject(address, "city", city);

       cJSON *postcode =  cJSON_CreateNumber(350000);             //创建基础类:string,并添加到对象中  
       cJSON_AddItemToObject(address, "postcode", postcode);

 //将内容存到CFGFILE文件中      
       FILE *fp = fopen(CFGFILE,"w");
       buf = cJSON_Print(object);
       fwrite(buf,strlen(buf),1,fp);
       fclose(fp);
       cJSON_Delete(object);
}


int main()
{
    test();
    printf("%s\n",buf);    //显示屏上查看对应的数据
}

           

执行指令:

  

gcc creat_json.c -o creat -lcjson

gcc creat_json.c cJSON.c -I ./ -o creat

实验二:解析文件

文件1:

conf_s_1.json

{
    "name": "T1","type": "s1"
}
           

文件2:

conf_s_2.json

{
    "T": [
    {"name": "T1","type": "s1"},
    {"name": "T2","type": "s2"}
    ]
}
           
#include <stdlib.h>
#include <stdio.h>
#include "cJSON.h"
#define CFGFILE1 "conf_s_1.json"  
#define CFGFILE2 "conf_s_2.json"           



void test1(){
//将文本内容存到字符串data中
       //打开文件
       FILE* file = fopen(CFGFILE1,"rb");
       //获得文件长度
       fseek(file,0,SEEK_END);
       long len=ftell(file);
       fseek(file,0,SEEK_SET);
       //开辟内存,将文件内容读取到data中
       char* data = (char*)malloc(len+1);            
       fread(data,1,len,file);
       //关闭文件
       fclose(file);
       
       cJSON* root = cJSON_Parse(data);                         //将cJSON实体呈现为用于传输/存储的文本

       cJSON* name_json = cJSON_GetObjectItem(root, "name");    //从对象中获取项目“string”。不区分大小写
       char* name = cJSON_Print(name_json);                     //将cJSON实体呈现为用于传输/存储的文本,而不进行任何格式化  
       printf("name:%s\n", name);
       free(name);     

       cJSON* type_json = cJSON_GetObjectItem(root, "type");    //从对象中获取项目“string”。不区分大小写
       char* type = cJSON_Print(type_json);                     //将cJSON实体呈现为用于传输/存储的文本,而不进行任何格式化        
       printf("type:%s\n", type);
       free(type);
       
       cJSON_Delete(root);                                     //删除一个cJSON实体和所有子实体
       free(data);                                             //删除内存
}

void test2(){
//将文本内容存到字符串data中
       //打开文件
       FILE* file = fopen(CFGFILE2,"rb");
       //获得文件长度
       fseek(file,0,SEEK_END);
       long len=ftell(file);
       fseek(file,0,SEEK_SET);
       //开辟内存,将文件内容读取到data中
       char* data = (char*)malloc(len+1);            
       fread(data,1,len,file);
       //关闭文件
       fclose(file);

       cJSON* root = cJSON_Parse(data);
     
       cJSON* OBJroot=cJSON_GetObjectItem(root, "T");
           
       int array_size = cJSON_GetArraySize(OBJroot);

       for (int i = 0; i < array_size; i++) {
              printf("i:%d\n", i);
              cJSON *object = cJSON_GetArrayItem(OBJroot, i);
              cJSON *name_json = cJSON_GetObjectItem(object, "name");
              char *name = cJSON_Print(name_json);    //将JSON结构体打印到字符串中 需要自己释放
              printf("name:%s\n", name);
              free(name);
              
              cJSON *type_json = cJSON_GetObjectItem(object, "type");
              char *type = cJSON_Print(type_json);    //将JSON结构体打印到字符串中 需要自己释放
              printf("type:%s\n", type);
              free(type);
              
       }
       cJSON_Delete(root);
       free(data);
}

int main()
{
       test1();
       //test2();   
}
           

实验三:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include"cJSON.h"

int main(void)
{
    char *string = "{\"family\":[\"father\",\"mother\",\"brother\",\"sister\",\"somebody\"]}";
    //从缓冲区中解析出JSON结构
    cJSON *json = cJSON_Parse(string);
    cJSON *node = NULL;
    //cJOSN_GetObjectItem 根据key来查找json节点 若果有返回非空
    node = cJSON_GetObjectItem(json,"family");
    if(node == NULL)
    {
        printf("family node == NULL\n");
    }
    else
    {
        printf("found family node\n");
    }
    node = cJSON_GetObjectItem(json,"famil");
    if(node == NULL)
    {
                printf("famil node == NULL\n");
        }
    else
        {
                printf("found famil node\n");
        }
    //判断是否有key是string的项 如果有返回1 否则返回0
    if(1 == cJSON_HasObjectItem(json,"family"))
    {
        printf("found family node\n");
    }
    else
    {
        printf("not found family node\n");
    }
    if(1 == cJSON_HasObjectItem(json,"famil"))
        {
                printf("found famil node\n");
        }
        else
        {
                printf("not found famil node\n");
        }

    node = cJSON_GetObjectItem(json,"family");
    if(node->type == cJSON_Array)
    {
        printf("array size is %d",cJSON_GetArraySize(node));
    }
    //非array类型的node 被当做array获取size的大小是未定义的行为 不要使用
    cJSON *tnode = NULL;
    int size = cJSON_GetArraySize(node);
    int i;
    for(i=0;i<size;i++)
    {
        tnode = cJSON_GetArrayItem(node,i);
        if(tnode->type == cJSON_String)
        {
            printf("value[%d]:%s\n",i,tnode->valuestring);
        }
        else
        {
            printf("node' type is not string\n");
        }
    }

    cJSON_ArrayForEach(tnode,node)
    {
        if(tnode->type == cJSON_String)
        {
            printf("int forEach: vale:%s\n",tnode->valuestring);
        }
        else
        {
            printf("node's type is not string\n");
        }
    }
    return 0;
}