一文搞定json解析和封装问题,手把手带你学习CJSON开源代码

Posted CodeBowl

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一文搞定json解析和封装问题,手把手带你学习CJSON开源代码相关的知识,希望对你有一定的参考价值。

努力成为大佬!

JSON简介

JSON 全称 javascript Object Notation,即 JS对象简谱,是一种轻量级的数据格式。

它采用完全独立于编程语言的文本格式来存储和表示数据,语法简洁、层次结构清晰,易于人阅读和编写,同时也易于机器解析和生成,有效的提升了网络传输效率。

JSON对象是一个无序的"名称/值"键值对的集合:

  • 以"{“开始,以”}"结束,允许**「嵌套使用」**;
  • 每个**「名称和值成对出现」**,名称和值之间使用":"分隔;
  • 键值对之间用","分隔
  • 在这些字符前后允许存在无意义的空白符;

对于键值,可以有如下值:

  • 一个新的**「json对象」**

  • 「数组」:使用"[“和”]"表示

  • 「数字」:直接表示,可以是整数,也可以是浮点数

  • 「字符串」:使用引号"表示

  • 「字面值」:false、null、true中的一个(必须是小写)

    示例:

  • {
        "students" : [ {
                "name" : "XiaoMing",
                "age" : 10,
                "learning" : true
            }, {
                "name" : "XiaoHong",
                "age" : 11,
                "learning" : false
            } ]
    }
    
    

cJSON介绍

cJSON是一个使用C语言编写的JSON数据解析器,具有超轻便,可移植,单文件的特点,使用MIT开源协议。

cJSON项目托管在Github上,仓库地址如下:

https://github.com/DaveGamble/cJSON

可以看到里面的文件比较多,其中cJSON的源码文件只有两个:

  • cJSON.h

  • cJSON.c

    使用的时候,只需要将这两个文件复制到工程目录,然后包含头文件cJSON.h即可,如下:

    #include "cJSON.h"
    

基本使用操作

关键数据结构

/* The cJSON structure: */
typedef struct cJSON
{
    /* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */
    struct cJSON *next;
    struct cJSON *prev;
    /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */
    struct cJSON *child;

    /* The type of the item, as above. */
    int type;

    /* The item's string, if type==cJSON_String  and type == cJSON_Raw */
    char *valuestring;
    /* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */
    int valueint;
    /* The item's number, if type==cJSON_Number */
    double valuedouble;

    /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */
    char *string;
} cJSON;

设计为一个结点结构体,有前后结点和孩子结点,type是该结点存放的数据类型,后面依次为数据存储。

设计思想

**根据上面结构体定义,设计的主要思想就是链表。**通过前后结点遍历,同时使用child*支持嵌套。

首先,它不是将一整段JSON数据抽象出来,而是将其中的一条JSON数据抽象出来,也就是一个键值对,用上面的结构体 strcut cJSON 来表示;
其次,一段完整的JSON数据中由很多键值对组成,并且涉及到键值对的查找、删除、添加,所以使用链表来存储整段JSON数据,如上面的代码所示:

next指针:指向下一个键值对
prev指针:指向上一个键值对

最后,因为JSON数据支持嵌套,所以一个键值对的值会是一个新的JSON数据对象(一条新的链表),也有可能是一个数组,方便起见,在cJSON中,数组也表示为一个数组对象,用链表存储,所以:

在键值对结构体中,当该键值对的值是一个嵌套的JSON数据或者一个数组时,由child指针指向该条新链表。

JSON数据解析

解析JSON数据的过程,其实就是剥离一个一个链表节点(键值对)的过程。

用于**「将字符串解析成json对象」**,若失败则返回NULL。

cJSON *cJSON_Parse(const char *value);

用于**「获取json对象中的某个节点」**,若失败,返回NULL,成功则返回该节点对象。

cJSON *cJSON_GetObjectItem(cJSON *object,const char *string);

用于释放json对象相关内存。

void   cJSON_Delete(cJSON *c);

如果JSON数据的值是数组,可以通过下面接口获取JSON 数组大小和数组里面的JSON 对象

int cJSON_GetArraySize(const cJSON *array);
cJSON * cJSON_GetArrayItem(const cJSON *array, int index);

解析步骤

​ 从网上看到一个比较有趣的王者荣耀的json,下面以它为例:

[{
 "ename": 105,
 "cname": "廉颇",
 "title": "正义爆轰",
 "new_type": 0,
 "hero_type": 3,
 "skin_name": "正义爆轰|地狱岩魂"
}, {
 "ename": 106,
 "cname": "小乔",
 "title": "恋之微风",
 "new_type": 0,
 "hero_type": 2,
 "skin_name": "恋之微风|万圣前夜|天鹅之梦|纯白花嫁|缤纷独角兽"
}]

  1. 通过cJSON接口解析buffer中的字符串
  2. 获取JSON指定字段

解析示例:

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

char *message =
"[								\\
{								\\
\\"ename\\": 105,                 \\
\\"cname\\" : \\"廉颇\\",			\\
\\"title\\" : \\"正义爆轰\\",		\\
\\"new_type\\" : 0,				\\
\\"hero_type\\" : 3,				\\
\\"skin_name\\" : \\"正义爆轰|地狱岩魂\\"	\\
},								\\
{								\\
	\\"ename\\": 106,					\\
	\\"cname\\" : \\"小乔\\",			\\
	\\"title\\" : \\"恋之微风\\",		\\
	\\"new_type\\" : 0,				\\
	\\"hero_type\\" : 2,				\\
	\\"skin_name\\" : \\"恋之微风|万圣前夜|天鹅之梦|纯白花嫁|缤纷独角兽\\"  \\
}									\\
]";

int main(void)
{
	cJSON *pJson = NULL;
	cJSON *pVal = NULL;
	cJSON *pTemp = NULL;
	/*创建cJSON对象*/
	pJson = cJSON_Parse(message);
	if (NULL == pJson)
	{
		printf("parse json failed\\n");
		return -1;
	}
	/*获取cJSON数组数量*/
	int num = cJSON_GetArraySize(pJson);
	/*遍历每一个cJSON数组元素*/
	for (int index = 0; index<num; index++)
	{
		/*获取cJSON数组中的第index个cJSON对象*/
		pTemp = cJSON_GetArrayItem(pJson, index);

		/*获取cJSON对象中的key值为ename的对象*/
		pVal = cJSON_GetObjectItem(pTemp, "ename");
		printf("ename:%d\\n", pVal->valueint);

		pVal = cJSON_GetObjectItem(pTemp, "cname");
		printf("cname:%s\\n", pVal->valuestring);

		pVal = cJSON_GetObjectItem(pTemp, "title");
		printf("title:%s\\n", pVal->valuestring);

		pVal = cJSON_GetObjectItem(pTemp, "new_type");
		printf("new_type:%d\\n", pVal->valueint);

		pVal = cJSON_GetObjectItem(pTemp, "hero_type");
		printf("hero_type:%d\\n", pVal->valueint);

		pVal = cJSON_GetObjectItem(pTemp, "skin_name");
		printf("skin_name:%s\\n\\n", pVal->valuestring);
		printf("====================================\\n\\n");
	}

	/*释放内存*/
	cJSON_Delete(pJson);
	pJson = NULL;

	return 0;
}

本示例,演示了解析数组,还有解析单项的操作,其他解析嵌套等操作,大家可以自由探索。

在本示例中,因为我提前知道数据的类型,比如字符型或者浮点型,所以我直接使用指针指向对应的数据域提取,在实际使用时,如果提前不确定数据类型,应该先判断type的值,确定数据类型,再从对应的数据域中提取数据

json数据封装

封装JSON数据的过程,其实就是**「创建链表」「向链表中添加节点」**的过程。

首先来讲述一下链表中的一些术语:

  • 「头指针」:指向链表头结点的指针;
  • 「头结点」:不存放有效数据,方便链表操作;
  • 「首节点」:第一个存放有效数据的节点;
  • 「尾节点」:最后一个存放有效数据的节点;

封装步骤

明白了这几个概念之后,我们开始讲述**「创建一段完整的JSON数据」,即如何「创建一条完整的链表」**。

  • 「创建头指针」
 cJSON* cjson_test = NULL;
  • 「创建头结点」,并将头指针指向头结点:
cjson_test = cJSON_CreateObject();
  • ③ 尽情的向链表中**「添加节点」**:
/* 添加一个值为 null 的布尔类型的JSON数据(添加一个链表节点) */
cJSON_AddNullToObject(cJSON * const object, const char * const name);
/* 添加一个值为 true 的布尔类型的JSON数据(添加一个链表节点) */
cJSON_AddTrueToObject(cJSON * const object, const char * const name);
/* 添加一个值为 False 的布尔类型的JSON数据(添加一个链表节点) */
cJSON_AddFalseToObject(cJSON * const object, const char * const name);
/* 添加一个值为布尔类型的JSON数据 0:false 非0:true (添加一个链表节点) */
cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean);
/* 添加一条数值类型的JSON数据(添加一个链表节点) */
cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number);
/* 添加一条字符串类型的JSON数据(添加一个链表节点) */
cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string);
/* 添加一行数据(添加一个链表节点) */
cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw);
/* 添加一个空对象(添加一个链表节点) */
cJSON_AddObjectToObject(cJSON * const object, const char * const name);
/* 添加一个空数组(添加一个链表节点) */
cJSON_AddArrayToObject(cJSON * const object, const char * const name);
/* 添加一个嵌套的JSON对象/数组(添加一个链表节点) */
cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item);
cJSON还提供了将JSON对象转换成字符串输出到终端
char *cJSON_Print(const cJSON *item);

封装示例

#include <stdio.h>
#include "cJSON.h"
int main(void)
{
    cJSON* cjson_test = NULL;
    cJSON* cjson_address = NULL;
    cJSON* cjson_skill = NULL;
    char* str = NULL;

    /* 创建一个JSON数据对象(链表头结点) */
    cjson_test = cJSON_CreateObject();

    /* 添加一个值为 null 的布尔类型的JSON数据(添加一个链表节点) */
    cJSON_AddNullToObject(cjson_test, "null_test");

    /* 添加一个值为 true 的布尔类型的JSON数据(添加一个链表节点) */
    cJSON_AddTrueToObject(cjson_test,"true_test");
    /* 添加一个值为 False 的布尔类型的JSON数据(添加一个链表节点) */
    cJSON_AddFalseToObject(cjson_test, "false_test");
    /* 添加一个值为布尔类型的JSON数据 0:false 非0:true (添加一个链表节点) */
    cJSON_AddBoolToObject(cjson_test, "bool_test", 0);

    /* 添加一条整数类型的JSON数据(添加一个链表节点) */
    cJSON_AddNumberToObject(cjson_test, "int_test", 22);

    /* 添加一条浮点类型的JSON数据(添加一个链表节点) */
    cJSON_AddNumberToObject(cjson_test, "double_test", 55.5);

    /* 添加一条字符串类型的JSON数据(添加一个链表节点) */
    cJSON_AddStringToObject(cjson_test, "str_test", "我是字符串");

    /* 添加一行任意数据(添加一个链表节点) */
    cJSON_AddRawToObject(cjson_test, "key", "任意数据");

    /* 添加一个空对象(添加一个链表节点) */
    cJSON_AddObjectToObject(cjson_test, "objet");

    /* 添加一个嵌套的JSON对象(添加一个链表节点) */
    cjson_address = cJSON_CreateObject();
    cJSON_AddStringToObject(cjson_address, "country", "China");
    cJSON_AddNumberToObject(cjson_address, "zip-code", 111111);
    cJSON_AddItemToObject(cjson_test, "address", cjson_address);

    /* 添加一个空数组(添加一个链表节点) */
    cJSON_AddArrayToObject(cjson_test, "Array");

    /* 添加一个数组类型的JSON数据(添加一个链表节点) */
    cjson_skill = cJSON_CreateArray();
    cJSON_AddItemToArray(cjson_skill, cJSON_CreateString( "C" ));
    cJSON_AddItemToArray(cjson_skill, cJSON_CreateString( "C++" ));
    cJSON_AddItemToArray(cjson_skill, cJSON_CreateString( "Python" ));
    cJSON_AddItemToArray(cjson_skill, cJSON_CreateString( "Java"));
    cJSON_AddItemToObject(cjson_test, "skill", cjson_skill);

    /* 打印JSON对象(整条链表)的所有数据 */
    str = cJSON_Print(cjson_test);
    printf("%s\\n", str);

    return 0;
}

使用注意事项

内存及时释放

cJSON的所有操作都是基于链表的,所以cJSON在使用过程中大量的使用malloc从堆中分配动态内存的,所以在使用完之后,应当及时调用下面的函数,清空cJSON指针所指向的内存,该函数也可用于删除某一条数据:

(void) cJSON_Delete(cJSON *item);

注意:该函数删除一条JSON数据时,如果有嵌套,会连带删除。

内存钩子

cJSON在支持自定义malloc函数和free函数,方法如下:

① 使用cJSON_Hooks来连接自定义malloc函数和free函数:

typedef struct cJSON_Hooks
{
      /* malloc/free are CDECL on Windows regardless of the default calling convention of the compiler, so ensure the hooks allow passing those functions directly. */
      void *(CJSON_CDECL *malloc_fn)(size_t sz);
      void (CJSON_CDECL *free_fn)(void *ptr);
} cJSON_Hooks;


② 初始化钩子cJSON_Hooks

(void) cJSON_InitHooks(cJSON_Hooks* hooks);

阅读心得

该项目是纯c编写的,运用了大量的指针和动态申请内存,以及一些语言特性static、const的运用,作为C或C++的学习项目还挺有用的。

下面是总结:

比较巧妙的地方

1.链表的使用

2.hooks的使用

typedef struct cJSON_Hooks
{
      /* malloc/free are CDECL on Windows regardless of the default calling convention of the compiler, so ensure the hooks allow passing those functions directly. */
      void *(CJSON_CDECL *malloc_fn)(size_t sz);
      void (CJSON_CDECL *free_fn)(void *ptr);
} cJSON_Hooks;
typedef struct internal_hooks
{
    void *(CJSON_CDECL *allocate)(size_t size);
    void (CJSON_CDECL *deallocate)(void *pointer);
    void *(CJSON_CDECL *reallocate)(void *pointer, size_t size);
} internal_hooks;

json的设计格式

如何用c编写json格式的语言,我找到了作者设计时候的参考图,大家可以简单了解一下:

object是名称/值 的无序集合:

数组的设计:

键值的设计:

string是一个零个或多个Unicode字符的序列,用双引号包装,使用反斜杠转义。字符表示为单个字符串。字符串非常类似于C或Java字符串。

数字 非常类似于C或Java数字,只是不使用八进制和十六进制格式。

空格可以插入到任意对令牌之间。除了一些编码细节外,这完全描述了语言。

可以看到都是用空格做间隔的。

char *message =
"[								\\
{								\\
\\"ename\\": 105,                 \\
\\"cname\\" : \\"廉颇\\",			\\
\\"title\\" : \\"正义爆轰\\",		\\
\\"new_type\\" : 0,				\\
\\"hero_type\\" : 3,				\\
\\"skin_name\\" : \\"正义爆轰|地狱岩魂\\"	\\
},								\\
{								\\
	\\"ename\\": 106,					\\
	\\"cname\\" : \\"小乔\\",			\\
	\\"title\\" : \\"恋之微风\\",		\\
	\\"new_type\\" : 0,				\\
	\\"hero_type\\" : 2,				\\
	\\"skin_name\\" : \\"恋之微风|万圣前夜|天鹅之梦|纯白花嫁|缤纷独角兽\\"  \\
}									\\
]";

参考资料

一款轻量级的JSON解析库,用cJSON让你看清王者荣耀

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

以上是关于一文搞定json解析和封装问题,手把手带你学习CJSON开源代码的主要内容,如果未能解决你的问题,请参考以下文章

Spring整合Mybatis一文讲透,手把手带你实操

专知国庆特刊-PyTorch手把手深度学习教程系列01一文带你入门优雅的PyTorch

RabbitMQ一文带你搞定RabbitMQ延迟队列

一文带你搞懂RPC到底是个啥

Spring Boot 如果防护 XSS + SQL 注入攻击 ?一文带你搞定!

手把手带你搞定C语言实现三子棋游戏,让你的代码有趣起来(超详细教程,从思路到代码,快码起来!)