写在前面的话:本文档旨在归纳总结个人的学习经验与成果,记录自己的成长,随便给大家分享自己解决的思路,为在这方面有需要的朋友提供一个参考。本人能力有限水平一般,文中难免会有一些错误,希望大家抱着辩证客观的态度来阅读,有错误还请各位海涵包容并予以批评指正。大家要是没兴趣看教程可以直接下载代码测试使用:https://download.csdn.net/download/qq_33784286/14933156
我相信这是一句废话,大家既然能搜索到这篇文章,必定是对json有了一定的了解,这里我直接贴出百度上的内容:
JSON(JavaScript Object Notation, JS 对象简谱) 是一种轻量级的数据交换格式。它基于 ECMAScript (欧洲计算机协会制定的js规范)的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。 易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。
这说的很笼统,很模糊,至少我认为是这样的,那么我说一下我的理解,我认为json是一种数据交换通信中作为数据包协议的数据交换格式。那么要作为一种通用的数据交换格式他就一定满足特定的特征才能应用在实际项目中。
说到这里就不得不提以下json的数据格式了,其实json的数据格式特别简单,相信经过我的总结,大家都能对json格式有一个比较清晰的认识。
Json数据格式解读:“2种数据结构4种数据类型”
2种数据结构:对象(object)、数组(arrary)
4种数据类型:字符串(string)、整形数字(int)、浮点型数字(double)、布尔(bool)
对象(object):由一对大括号(“{}”)包括起来的一组键值对(键值对:由键名(keyName)和键值(keyValue)构成,在json中,键值可以是“对象”,“数组”,“字符串”,“整形数字”,“浮点型数字”,“布尔”构成),也就是说“对象”这种数据结构的特点就是开始和结尾分别有“{”和“}”来提示“对象”的开始和结束,而对象里的内容是一组组键值对。例如:
{
"object":"hello world",
"intNum":123,
"doubleNum":123.123,
"bool":true
}
通过上面的例子可以看到键名是由字符串构成,而键名和键值之间是由“:”分隔开,键值对与键值对之间是由“,”区分,在对象的最后一个键值对后面无需加上“,”,这些都是json中特定的格式,可以用来判断json字符串是否合法。
数组(arrary):由一对中括号(“[]”)包括起来的一组键值,注意这里讲的是键值而不是键值对,因为在数组中每个元素的位置可以由下标来表示,所以键名在这显得特别多余,因此在数组中只要键值而不是键值对。例如:
[
"hello world",
123,
123.123,
true
]
同样的,在数组中,键值与键值间由“,”区分,最后一个键值后面无需加上“,”。
对象和数组可以相互包括彼此,一般来说不限制嵌套深度。例如:
{
"object":"json object",
"time":"2021.01.25",
"address":"guangZhou,guangDong",
"name":"renJia Lu",
"object-test":
{
"string":"hello world",
"intNum":123456,
"doubleNum":3.141592,
"bool_1":true,
"bool_2":false,
"arrary":
[
"this is object.table-string",
857857,
857.857,
true,
false,
[
"this is object.table.table-string",
996996,
996.996,
true,
false
]
]
},
"table-test":
[
"this is table-string",
886886,
886.886,
false,
true,
{
"string":"this is table.object-string",
"intNum":123456,
"doubleNum":3.141592,
"bool_1":true,
"bool_2":false
}
]
}
相信看到这里,大家都对json的数据结构都有了个大概,那么接下来我们开始来将这一段json格式的字符串解析成json格式的数据结构。
很容易想到json数据结构与咱们C语言中的结构体比较像,那么我们可以用结构体来替代json数据结构,那么咱们的结构体要有哪些元素呢?这里一步步将怎么来的太麻烦了,我直接贴出我定义的结构体,带着答案来给大家简单分析一下。
typedef struct cJSON //cjson基本结点结构体
{
struct cJSON* next,*last;
struct cJSON* child;
unsigned char *keyName;
Json_type keyValueType;
unsigned char *valueString;
int valueInt;
double valueDouble;
Json_Bool_type valueBool;
}cJSON,*cJSON_P;
next: 表示下一个键值对的指针(其实是链表下一个结点的指针)
last: 表示上一个键值对的指针(其实是链表上一个结点的指针)
child: 用于对象或者数组的子结点(稍后解释)
keyName: 键值名
keyValueType: 键值类型(7种 稍后解释)
valueString: 用于存储字符串型键值
valueInt: 用于存储整形数字键值
valueDouble: 用于存储浮点型数字数值
valueBool: 用于存储布尔型值
可能大家看到这里有点疑问了,还要用到链表?答案是的,因为我们在解析前对该json字符串是出于一种未知状态的,我们不知道他需要多大空间,不知道他的键值对有多少等等等等问题,那么面对这种问题,我们的解决策略就是采用链表来处理。我们的解决思路是一个键值对就是链表中的一个结点,然后通过next和last指针来链接其整个数据结构,而其中还有对象或者数组这种嵌套的数据我们通过引入一个child指针来解决,也就是说在解析过程中把对象和数组也当成一个键值类型来处理,那么可能又有朋友要问了,及时把对象和数组当成键值类型来处理也只有6中键值类型啊,上面为什么说有7种键值类型啊?其实我没有讲错,我在做程序的时候考虑到一种空值就是NULL。接下来看看我的键值类型的定义吧。
typedef enum //cjson 数据类型
{
Json_NULL, //空
Json_Object, //对象
Json_Arrary, //数组
Json_String, //字符串
Json_Number_Int, //整形
Json_Number_Double, //浮点型
Json_Bool, //布尔型
}Json_type;
上面的结构体如果大家不清楚就请好好琢磨琢磨吧!一定要整理清楚才行!!!
/**********************************************************************************************/
/******************************等待大家理清楚结构体逻辑*********************************/
/**********************************************************************************************/
到这里,大家应该都理清楚了结构体的逻辑了,那么接下来我们就正式开始解析吧,首先我们拿到这样一串字符串,我们从第一个字节开始分析,首先作为json字符串的话最外层默认是没有名字的对象,也就是说第一个字符应该是“{”,如果不是那么字符串就是不合法的,直接报错就OJBK了。
接下来我们开始对第一次对象开始解析,对象里面的数据一定是键值对,也就是说一定是有键名和键值,如果没有就可以宣告字符串非法,解析失败了。那么对于一个键值对来说,首先我们应该获取它的键名,键名是由字符串构成的,那么解析键名一定是以“””开始和结束的,键名后面一定是紧紧跟着“:”的,否则就是非法的;那么接下来就可以解析键值了,解析键值的时候可以根据他的第一个字节来判断键值对类型,合法的字符串只可能有一下几种类型
那么得到键值类型后就可以把键值解出来了,如果是对象型或者数组型的,就进入child结点解析,直到解析的键值后面跟的是}或者]就可以退出child了,如果解析过程中出现错误或者解析到空字符那就完了,这个字符串又是非法的了,你想想你是以{进去的最后出来不是},这个字符串是不是有问题?
其他的类型各有个的特点,例如数组不可能出现键名,数字不可能出现除了’0’-‘9’、’-’、‘.‘以外的字符,并且‘-’只可能出现一个在最前面,‘.’前面必须要有数字,并且只能出现一次等等,根据这些特点可以把所有的键值都给解出来放到对应的变量里面。这里我就不一一说明了。
不过这样说起来是很容易,这中间涉及到动态内存分配与释放,结构体,链表,函数递归,真的做起来的时候还是有点绕人的,大家只要仔细的写应该还是没有问题的,相信大家的C语言能力都比我要厉害,在我写的代码中,用来解析json字符串的函数是:
/*
函数名:cJson_parse_string_to_object
传入参数:unsigned char *cJson_string 将被转换的json字符串
返回参数:cJSON_P: [CJSON_NULL:转换失败,other:转换成功,返回json数据结构句柄]
函数功能:将json字符串转换成json数据结构
*/
cJSON_P cJson_parse_string_to_object (const unsigned char *cJson_string)//将json字符串转换成json数据结构
在我的代码中注释得很详细,大家可以下载下来看看。
解析会了,那么我们构析就很简单了,只需要根据键值类型添加相应的特点字符就行了,例如对象类型的就需要在字符串前后加上{}包括起来,其他的如是,由于篇幅的关系,我就不详细讲解解析过程了,因为解析过程相对简单,只要了解了json的数据结构与json的组包特点,相信大家都很简单能构析出来,在我的代码中,将json数据结构构析成一个json字符串的函数是:
/*
函数名:cJson_parse_object_to_string
传入参数:cJSON_P json 将被转换的json数据结构的句柄
返回参数:unsigned char*: [CJSON_NULL:转换失败,other:转换成功,返回字符串指针]
函数功能:将json数据结构转换成json字符串
*/
unsigned char* cJson_parse_object_to_string (cJSON_P json) //将cJson数据结构转换成json字符串
其实一般来说大家有这两个函数就足够满足工作需求了,不过我考虑到大家可能会有更高的需求,于是我做了一整套我认为会用到的功能,包括解析、构析、打印、删除、增添、修改、构建等功能封装成函数供大家使用,大家有兴趣可以自己研究一下,这些功能我自己都测试过了,大家可以放心的使用,当然也欢迎大家对这份代码进行测试并反馈问题,大家共同进步。
/*
函数名:printf_cJson
传入参数:cJSON_P json 需要打印的json数据结构地址
返回参数:Json_err_t: [Json_ERR:数据打印失败,Json_OK:数据打印成功]
函数功能:根据传入的json数据结构地址将json结构的数据打印出来
*/
Json_err_t printf_cJson (cJSON_P json) //打印cJson数据结构
/*
函数名:printf_cJson_key_node
传入参数:cJSON_P json 需要打印的json数据结点
返回参数:Json_err_t: [Json_ERR:数据打印失败,Json_OK:数据打印成功]
函数功能:根据传入的json数据结构地址将对应的json结点数据打印出来,仅仅打印单个结点数据
*/
Json_err_t printf_cJson_key_node (cJSON_P json) //打印cJson数据结构节点
/*
函数名:cJson_parse_string_to_object
传入参数:unsigned char *cJson_string 将被转换的json字符串
返回参数:cJSON_P: [CJSON_NULL:转换失败,other:转换成功,返回json数据结构句柄]
函数功能:将json字符串转换成json数据结构
*/
cJSON_P cJson_parse_string_to_object (const unsigned char *cJson_string)//将json字符串转换成json数据结构
/*
函数名:cJson_parse_object_to_string
传入参数:cJSON_P json 将被转换的json数据结构的句柄
返回参数:unsigned char*: [CJSON_NULL:转换失败,other:转换成功,返回字符串指针]
函数功能:将json数据结构转换成json字符串
*/
unsigned char* cJson_parse_object_to_string (cJSON_P json) //将cJson数据结构转换成json字符串
/*
函数名:cJson_find_keyName_node
传入参数:cJSON_P json 被查找的json数据结构的句柄
const unsigned char*keyName 被查找的结点路径名称 格式 object.arrary[0]
返回参数:cJSON_P: [CJSON_NULL:查找失败,other:查找成功,返回对应结点json句柄]
函数功能:根据结点路径在指定json数据结构中查找结点,并返回其结点句柄
*/
cJSON_P cJson_find_keyName_node (cJSON_P json,const unsigned char*keyName) //根据键值名查找键值对所在的节点
/*
函数名:cJson_delete
传入参数:cJSON_P 被删除的json数据结构起始结点地址(必须是整个json结构的起始地址)
返回参数:Json_err_t: [Json_ERR:删除json失败,Json_OK:删除json成功]
函数功能:删除json数据结构
*/
Json_err_t cJson_delete (cJSON_P json) //删除整个数据结构
/*
函数名:cJson_delete_key_node
传入参数:cJSON_P *json 被删除的json结点的结点地址指针
返回参数:Json_err_t: [Json_ERR:删除json结点失败,Json_OK:删除json结点成功]
函数功能:删除json单个结点,如果该结点时object或者arrary类型,则将其下面的child结点一并删除
*/
Json_err_t cJson_delete_key_node (cJSON_P *json) //根据节点地址删除对应节点
/*
函数名:cJson_delete_key_node
传入参数:cJSON_P *json 被删除的json结点所在的json数据结构的句柄指针
const unsigned char*keyName 要被删除的json结点的路径名称 格式 object.arrary[0]
返回参数:Json_err_t: [Json_ERR:删除json结点失败,Json_OK:删除json结点成功]
函数功能:删除json单个结点,如果该结点时object或者arrary类型,则将其下面的child结点一并删除
*/
Json_err_t cJson_delete_node_for_keyName (cJSON_P *json,const unsigned char*keyName) //根据键名删除所在节点
/*
函数名:cJson_change_node
传入参数:cJSON_P json 被修改的json结点地址
const void* value 将被更新的结点值
Json_type value_type 被更新的结点值的类型属性
返回参数:Json_err_t: [Json_ERR:更新json结点数据失败,Json_OK:更新json结点数据成功]
函数功能:指定json结点更新json结点数据
*/
Json_err_t cJson_change_node (cJSON_P json,const void* value,Json_type value_type) //根据结点修改对应值
/*
函数名:cJson_change_keyName_node
传入参数:cJSON_P json 被修改的json结点的句柄地址
const unsigned char*keyName 将被更新的结点路径地址 格式 object.arrary[0]
const void* value 将被更新的结点值
Json_type value_type 被更新的结点值的类型属性
返回参数:Json_err_t: [Json_ERR:更新json结点数据失败,Json_OK:更新json结点数据成功]
函数功能:更新json结点数据
*/
Json_err_t cJson_change_keyName_node (cJSON_P json,const unsigned char*keyName,const void* value,Json_type value_type) //根据键值名修改对应值
/*
函数名:cJson_addition_node
传入参数:cJSON_P json 被新增的json结点的前一个结点
cJSON_P node 被新增的json结点
返回参数:Json_err_t: [Json_ERR:新增json结点失败,Json_OK:新增json结点成功]
函数功能:在指定json结点后面新增node数据结点
*/
Json_err_t cJson_addition_node (cJSON_P json,cJSON_P node) //在json后添加node
/*
函数名:cJson_addition_node_for_keyName
传入参数:cJSON_P json 被新增的json结点所在的json句柄
const unsigned char*keyName 被新增的json结点的前一个结点的路径名称 格式 object.arrary[0]
cJSON_P node 被新增的json结点
返回参数:Json_err_t: [Json_ERR:新增json结点失败,Json_OK:新增json结点成功]
函数功能:在指定json数据结构中的keyName后面新增node数据结点
*/
Json_err_t cJson_addition_node_for_keyName (cJSON_P json,const unsigned char*keyName,cJSON_P node) //根据键值名 在后面添加数据
/*
函数名:cJson_build_node
传入参数:const unsigned char*keyName 被构建的json数据结点的名称
void* value 被构建的数据结点的值
Json_type value_type 被构建的json数据结点的值的属性
返回参数:cJSON_P: [CJSON_NULL:构建json数据结点失败,other:构建的json数据结点成功 返回新构建的数据结点的句柄]
函数功能:构建一个新的json数据结点
*/
cJSON_P cJson_build_node (const unsigned char*keyName,void* value,Json_type value_type) //创建一个json数据结构体