零基础学习cJSON 源码详解与应用(二)创建json数据

本文主要介绍了cJSON创建简单JSON数据的方法,包括确定item类型、创建并添加到root节点。详细解析了相关源码,如创建对象、字符串等函数。还提醒注意const和reference的使用,避免内存错误。最后给出更简单的创建cJSON方式,帮助读者掌握JSON与cJSON结构体对应关系。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

cJSON系列:

上一篇我们已经介绍了cjson结构体 (一)如何学习cJSON,这节就介绍简单的创建json数据并分析源码。

一,创建一个简单的json

例如创建一个如下的json:

{
    "years": 22,
    "name": "fool",
    "man": true,
    "adult": false,
    "money": null,
    "season": ["spring", "summer", "fall","winter"],
    "child": {
        "girlfriend": "june"
    }
}

首先分析这个json,确定它的所有item类型,其中比较需要注意的是child这个item是一个嵌套子json,所以它的类型应该是object,从item的类型入手,先创建所有的item,再把item添加到root节点上。代码如下:

    char *str_arry[3] = 
    {
        "spring",
        "summer",
        "fall",
        "winter"
    };
    //创建不同类型的item
    cJSON *root = cJSON_CreateObject();
    cJSON *years = cJSON_CreateNumber(22);
    cJSON *name = cJSON_CreateString("fool");
    cJSON *man = cJSON_CreateTrue();
    cJSON *adult = cJSON_CreateFalse();
    cJSON *money = cJSON_CreateNull();
    cJSON *season = cJSON_CreateStringArray(str_arry, 3);
    cJSON *child = cJSON_CreateObject();
    cJSON *girlfriend = cJSON_CreateString("june");

    //将item添加到父节点下
    cJSON_AddItemToObject(root, "years", years);
    cJSON_AddItemToObject(root, "name", name);
    cJSON_AddItemToObject(root, "man", man);
    cJSON_AddItemToObject(root, "adult", adult);
    cJSON_AddItemToObject(root, "money", money);
    cJSON_AddItemToObject(root, "season", season);
    cJSON_AddItemToObject(child, "girlfriend", girlfriend);
    cJSON_AddItemToObject(root, "child", child);

    //将所有json结构体输出成字符串
    char *json=cJSON_Print(root);

    printf("%s\n",json);

    //! 一定要记得用完擦屁股
    cJSON_Delete(root);

这里需要注意的是由于cjson是使用动态内存分配的,当cjson用完后,切记!切记!切记!一定用要用cJSON_Delete释放内存

打印结果如下:

在这里插入图片描述

二,源码详解

1, CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void);

//创建一个空的json,赋予obj类型
CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void)
{
    cJSON *item = cJSON_New_Item(&global_hooks);
    if (item)
    {
        item->type = cJSON_Object;
    }

    return item;
}

该函数调用的是cJSON_New_Item(),传入参数&global_hooks是全局变量,该变量是有三个函数指针分别指向free(),malloc(),realloc(),用来操作内存的。
cJSON_New_Item()以动态内存分配的形式,申请了一块内存并将内存初始化。返回指向该内存的指针。

//动态内存分配一个cjson并设为0
static cJSON *cJSON_New_Item(const internal_hooks * const hooks)
{

    cJSON* node = (cJSON*)hooks->allocate(sizeof(cJSON));
    if (node)
    {
        memset(node, '\0', sizeof(cJSON));
    }

    return node;
}

2, CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string);

//创建字符串值为string的json,string值可被cjson_delete删除
CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string)
{
    //创建空cjson
    cJSON *item = cJSON_New_Item(&global_hooks);
    if(item)
    {
        item->type = cJSON_String;
        //将string内容复制给valuestring
        item->valuestring = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks);
        if(!item->valuestring)
        {
            cJSON_Delete(item);
            return NULL;
        }
    }

    return item;
}

cJSON_strdup()也是后续会经常使用的函数,作用是复制字符串。

//开辟一块新内存,并将string复制到内存中,并返回指向内存的指针
static unsigned char* cJSON_strdup(const unsigned char* string, const internal_hooks * const hooks)
{
    size_t length = 0;
    unsigned char *copy = NULL;

    if (string == NULL)
    {
        return NULL;
    }
    //计算所需内存的长度
    length = strlen((const char*)string) + sizeof("");
    //申请内存
    copy = (unsigned char*)hooks->allocate(length);
    if (copy == NULL)
    {
        return NULL;
    }
    memcpy(copy, string, length);

    return copy;
}

3,CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char **strings, int count);

/*
 * 创建字符串型数组的json
 * strings:字符串数组
 * count:数组长度
 * 返回:数组
 */
CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char **strings, int count)
{
    size_t i = 0;
    cJSON *n = NULL;    //next
    cJSON *p = NULL;    //prev
    cJSON *a = NULL;    //arry

    if ((count < 0) || (strings == NULL))
    {
        return NULL;
    }

    //创建数组
    a = cJSON_CreateArray();

    for (i = 0; a && (i < (size_t)count); i++)
    {
        //创建string类型cjson
        n = cJSON_CreateString(strings[i]);
        if(!n)
        {
            cJSON_Delete(a);
            return NULL;
        }
        if(!i)
        {
            //如果是数组第一个,则将n复制给数组cjson的子节点
            a->child = n;
        }
        else
        {
            //将n插入p之后(链表操作)
            suffix_object(p,n);
        }
        p = n;
    }

    return a;
}

4,CJSON_PUBLIC(void) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item);

cJSON_AddItemToObject()是调用了add_item_to_object()

/*
 * 将item以键string添加到obj下
 * note:item可以是任何类型的cjson
 * constant_key:指示string是否是外部定义的常量数据
 * 成功:返回 true
 */
static cJSON_bool add_item_to_object(cJSON * const object, const char * const string, cJSON * const item, const internal_hooks * const hooks, const cJSON_bool constant_key)
{
    char *new_key = NULL;
    int new_type = cJSON_Invalid;

    if ((object == NULL) || (string == NULL) || (item == NULL))
    {
        return false;
    }

    //判断key是否是常量数据,若是则不需要重新复制字符串
    if (constant_key)
    {
        //直接取指针,cjson_delete不会删除key
        new_key = (char*)cast_away_const(string);
        //cjson类型设置为常量字符串
        new_type = item->type | cJSON_StringIsConst;
    }
    else
    {
        //复制string
        new_key = (char*)cJSON_strdup((const unsigned char*)string, hooks);
        if (new_key == NULL)
        {
            return false;
        }
        //修改cjson类型
        new_type = item->type & ~cJSON_StringIsConst;
    }

    if (!(item->type & cJSON_StringIsConst) && (item->string != NULL))
    {
        //如果string是常量且不为空则释放内存
        hooks->deallocate(item->string);
    }

	//将key赋值给cjson的键
    item->string = new_key;
    item->type = new_type;

    //最后把item添加到obj
    return add_item_to_array(object, item);
}

在最后调用了add_item_to_array();

/*
 * 添加item到数组最后
 * 成功返回 true
 */
static cJSON_bool add_item_to_array(cJSON *array, cJSON *item)
{
    cJSON *child = NULL;

    if ((item == NULL) || (array == NULL))
    {
        return false;
    }

    //child是数组的第一个成员
    child = array->child;

    //如果数组为空,则item赋予第一个成员child
    if (child == NULL)
    {
        /* list is empty, start new one */
        array->child = item;
    }
    else
    {
        /* append to the end */
        //遍历数组,并在数组最后插入item
        while (child->next)
        {
            child = child->next;
        }
        suffix_object(child, item);
    }

    return true;
}

5,CJSON_PUBLIC(void) cJSON_Delete(cJSON *c);

/*
 * 将item所在链表的及其子节点的所有cjson释放
 */
CJSON_PUBLIC(void) cJSON_Delete(cJSON *item)
{
    cJSON *next = NULL; //指向当前cjson的下一个
    
    
    while (item != NULL)
    {
        //由json结构体的定义可知,json结构体自身,string和valuestring是由动态内存分配获得,需要释放这三部分内存
        next = item->next;
        //如果cjson不是参考外部数据且不为空,则递归调用delete,删除cjson的子节点
        if (!(item->type & cJSON_IsReference) && (item->child != NULL))
        {
            //释放嵌套的json结构体
            cJSON_Delete(item->child);
        }
        //如果cjson不是参考外部数据,则valuestring以及string是由cJSON动态内存分配的,需要释放内存
        if (!(item->type & cJSON_IsReference) && (item->valuestring != NULL))
        {
            global_hooks.deallocate(item->valuestring);
        }
        if (!(item->type & cJSON_StringIsConst) && (item->string != NULL))
        {
            global_hooks.deallocate(item->string);
        }
        //释放当前的cjson
        global_hooks.deallocate(item);
        //准备释放链表中的下一个cjson
        item = next;
    }
}

deallocate();可类比free();
这里需要注意的是释放内存时,要判断cjson结构体的string,valuesstring是否是常量。

当string,valuesstring是动态内存分配时,才能使用deallocate()释放,否则可能出现内存错误。具体原因见下一节

三 注意 const,reference

上面的代码中你会发现有些函数中会出现const,reference这是cjson允许使用外部定义的变量来作为cjson结构体的成员。这种情况下的字符串需要用const修饰,结构体用reference修饰。

cJSON_CreateString()为例,在为valuestring赋值时,使用的是动态内存分配的形式申请一块内存,传入参数string复制到内存,在这之后,参数string如何变化对该item的valuestring是没有影响的。

作为对比,以cJSON_CreateStringReference()为例:
在这个函数中,系统不为valuestring分配内存,而只是把它指向string

/*
 * 创建一个string类型的item,其valuestring指向传入参数string,外部定义的string发生变化会影响item的valuestring
 */
CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string)
{
    cJSON *item = cJSON_New_Item(&global_hooks);
    if (item != NULL)
    {
        item->type = cJSON_String | cJSON_IsReference;
        item->valuestring = (char*)cast_away_const(string);
    }

    return item;
}

这么做的目的一个方面是可以节约内存,但在使用时需注意参考变量的值不要轻易修改,其他reference类的函数一样,只是参考的变量不同。

CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child);

CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child);

CJSON_PUBLIC(void) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item);
CJSON_PUBLIC(void) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item);
CJSON_PUBLIC(void) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item);

由此造成:当删除一个item时,需要判断其指针是否是外部定义的变量,以此来决定是否使用内存释放函数。

四 更简单的创建cjson

当你熟悉了cjson对json的创建思路后,可用以下更加快的方式创建cjson:

    cJSON_AddNullToObject(root, "money");
    cJSON_AddTrueToObject(root, "man");
    cJSON_AddFalseToObject(root, "adult");
    cJSON_AddNumberToObject(root, "years", 22);
    cJSON_AddStringToObject(root, "name", "fool");

    cJSON *season = cJSON_CreateStringArray(str_arry, 3);
    cJSON_AddItemToObject(root, "season", season);
    
    cJSON *child = cJSON_AddObjectToObject(root, "child");
    cJSON_AddStringToObject(child, "girlfriend", "june");

CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string);

可见这个函数本质上是调用了前面提到的cJSON_CreateString();add_item_to_object();就是两步并作一步走。

/*
 * 添加string类型cjson到obj
 * name :键
 * string:值
 * 成功返回:string类cjson
 */
CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string)
{
    cJSON *string_item = cJSON_CreateString(string);
    if (add_item_to_object(object, name, string_item, &global_hooks, false))
    {
        return string_item;
    }

    cJSON_Delete(string_item);
    return NULL;
}

小结

熟悉完json的创建,对json与cjson结构体的对应关系有了更深的掌握,下一章将解析cJSON_Print();输出json。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值