使用CJSON库实现XML与JSON格式的相互转化

来源:互联网 发布:网络上的死鱼是啥意思 编辑:程序博客网 时间:2024/04/30 04:19

之前完成了一个两个平台对接的项目。由于这两个平台一个是使用json格式的数据,一个是使用xml格式的数据,要实现它们二者的对接就涉及到这两个数据格式的转化,在查阅相关资料的时候发现了这个CJSON库,cjson是使用c编写的,它轻巧易用,在网上查了相关的资料后决定在json格式的存储于解析这块采用cjson库,而xml就简单的来解析字符串。

cjson库中常用的几个函数简介

cJSON_Parse

该函数需要传入一个json格式的字符串,函数会将这个字符串转化为json格式保存起来,函数会返回一个表示json对象的指针,如果传入json格式字符串有误,函数会返回NULL,所以在之后如果要使用它生成的json对象的指针,一定要校验指针值

cJSON_CreateObject

创建一个json格式的对相关,用来保存之后的json格式数据

cJSON_CreateArray

创建一个json格式的数组

cJSON_AddItemToObject

将某个数据插入到对应的json对象中,函数需要三个参数,第一个参数是一个json对象,表示要往哪个json对象里面插入数据,第二个参数是一个字符串指针,表示该项的键值,第三个参数是一个json对象,表示要将何种对象插入到json对象中,这个函数一般是用来插入一个数组对象

cJSON_AddNumberToObject

对于插入数值,或者字符串值,如果调用cJSON_AddItemToObject,需要向将他们转化为json对象然后插入,为了方便库中提供了一个宏来方便插入数字值,它的参数与cJSON_AddItemToObject类似,只是最后一个参数是一个数字值

cJSON_AddStringToObject

将字符串插入json对象中,它的用法与cJSON_AddNumberToObject相同

cJSON_Print

将json对象转化为json格式的字符串

cJson_Delete

由于cjson对象是用malloc函数分配的内存,所以需要使用这个函数来释放分配的内存,否则会造成内存泄露。这个函数会释放对象中的所有内存单元,包括使用相关函数添加到对象中的子对象,所以在释放了对象的内存后,它的子对象的内存就不需要再次释放了

cJosn结构体

typedef struct cJSON{    struct cJSON *next;    struct cJSON *prev;    struct cJSON *child;    int type;    char *valuestring;    int valueint;    double valuedouble;    char *string;} cJSON;

cjson中采用该结构体来存储json格式的数据,这个结构体存储的是json格式的单个项,其中为了能存储所有常用类型的数据,在里面定义了三种类型的成员,分别表示不同的数据类型值,string 成员表示的是该项的键值;它里面的三个指针分别表示同级别的下一项,上一项以及它的子节点,这些值在遍历这个json对象中的数据时需要用到

具体的算法

json格式转化为xml格式

string CJson::Json2Xml(const string &strJson){    string strXml = "";    cJSON *pRoot = cJSON_Parse(strJson.c_str());    if (NULL == pRoot)    {        return "";    }    cJSON *pChild = pRoot->child;    while (pChild != NULL)    {        if (pChild->child != NULL) //存在子节点的情况        {            std::string strSubKey = pChild->string; //获取它的键            std::string strSubValue = Json2Xml(cJSON_Print(pChild)); //获取它的值            std::string strSubXml = "<" + strSubKey + ">" + strSubValue + "</" + strSubKey + ">";            strXml += strSubXml;        }else        {            std::string strKey = pChild->string;            std::string strVal = "";            if (pChild->valuestring != NULL)            {                string strTemp = pChild->valuestring;                strVal = "\"" + strTemp + "\"";            }else            {                //其余情况作为整数处理                strVal = cJSON_Print(pChild);            }            strXml = strXml + "<" + strKey + ">" + strVal + "</" + strKey + ">";        }        pChild = pChild->next;    }    if(NULL != pRoot)    {        cJson_Delete(pRoot);    }    return strXml;}

上述代码首先将传进来的json格式的字符串转化为json对象,然后再遍历这个json对象。cjson在存储json格式的数据时,首先利用一个空的cJson结构体来保存整个json格式,类似于存在头指针的链表,它的child节点指针指向的是里面的第一个成员的信息,所以在遍历之前需要将指针偏移到它的child节点处。这个遍历的整体思想是:依次遍历它的同级节点,分别取出它的键和值key、value,并且将这一项组织成类似于

<key>value</key>

它的同级节点以相同的字符串结构添加到它的后面。如果某个成员中有子节点,那么递归调用这个函数,,并将返回的值作为value,在它的两侧加上key的标签。另外在遍历的时候需要注意的是它的值,其实这块可以使用cjson结构中的type来做更精准的判断,之前我在写这块的代码的时候没有仔细的查看库的源代码,所以简单的利用valuestring指针来判断,如果是字符串那么在字符串的两侧加上引号,否则什么都不加,在生成的xml中只需要判断值中是否有引号,有则表示它是一个字符串,否则是一个数字类型的值

xml转json

//暂时不考虑xml标签中存在属性值的问题string CJson::Xml2Json(const string &strxml){    cJSON *pJsonRoot = cJSON_CreateObject();    string strNext = strxml;    int nPos = 0;    while ((nPos = strNext.find("<")) != -1)    {        string strKey = GetXmlKey(strNext);        string strValue = GetXmlValueFromKey(strNext, strKey);        string strCurrXml = strNext;        strNext = GoToNextItem(strNext, strKey);        int LabelPos = strValue.find("<"); // < 所在位置        int nMarkPos = strValue.find("\""); // " 所在位置        if (strValue != "" && LabelPos != -1 && LabelPos < nMarkPos) //引号出现在标签之后        {            //里面还有标签            string strNextKey = GetXmlKey(strNext);            //下一个的标签与这个相同,则为一个数组            if (strNextKey == strKey)            {                cJSON *pArrayObj = cJSON_CreateArray();                int nCnt = GetArrayItem(strCurrXml);                for (int i = 0; i < nCnt; i++)                {                    strKey = GetXmlKey(strCurrXml);                    strValue = GetXmlValueFromKey(strCurrXml, strKey);                    string strArrayItem = Xml2Json(strValue);                    cJSON *pArrayItem = cJSON_Parse(strArrayItem.c_str());                    cJSON_AddItemToArray(pArrayObj, pArrayItem);                    strCurrXml = GoToNextItem(strCurrXml, strKey);                }                cJSON_AddItemToObject(pJsonRoot, strNextKey.c_str(), pArrayObj);                strNext = strCurrXml;            }else            {                //否则为普通对象                string strSubJson = Xml2Json(strValue);                cJSON *pSubJsonItem = cJSON_CreateObject();                pSubJsonItem = cJSON_Parse(strSubJson.c_str());                cJSON_AddItemToObject(pJsonRoot, strKey.c_str(), pSubJsonItem);            }        }        else        {            if (strValue.find("\"") == -1) //这个是数字            {                cJSON_AddNumberToObject(pJsonRoot, strKey.c_str(), atof(strValue.c_str()));            }else            {                remove_char(strValue, '\"');                cJSON_AddStringToObject(pJsonRoot, strKey.c_str(), strValue.c_str());            }        }    }    string strJson = cJSON_Print(pJsonRoot);    cJson_Delete(pJsonRoot);    return strJson;}

就像注释上说的,这段代码没有考虑xml中标签存在属性的问题,如果考虑上的话,我的想法是将属性作为该项的子项,给子项对应的键名做一个约定,以某个规律来命名,比如”标签名_contrib”,这样在解析的时候一旦出现后面带有contrib的字符样式,就知道它是属性,后面就遍历这个子节点取出并以字符串的形式保存即可
算法的思想跟之前的类似,在这我定义了几个函数用来从xml中取出每一项的键,值信息,然后将这些信息保存到json对象中,最后生成一个完整的json对象,调用print函数将对象转化为json格式的字符串。
在while表示如果它的后面没有”<”表示后面就没有对应的值,这个时候就是xml格式的数据遍历完了,这个时候结循环中判断了下是否存在下一个标签,如果没有则结束循环,返回json格式字符串,函数返回。
在循环中依次遍历它的每一个标签,在第一个if判断中出现这样的语句strValue != “” && LabelPos != -1 && LabelPos < nMarkPos,strValue表示的是标签中的值,LabelPos表示值中出现”<”的位置,而nMarkPos表示引号出现的位置,结合它们三个变量表示的含义,其实这句话表示如果值里面有”<”并且这个出现在引号之前,那么就说明是标签套标签,也就是存在子标签,这个时候需要递归调用函数,解析子标签的内存,如果这个”<”符号出现在引号之后,则表示它只是值中字符串的一部分,并没有子标签,这个时候就不需要进行递归。
另外还判断了是否存在数组的情况,在json中数组是以一个类似于子对象的方式存储的,所在转化为xml时会将它作为一个子项存储,只是它的标签于父项的标签相同,所以判断数组的语句是当它存在子项时进行的,当得到它是一个数组时,会往后一直遍历,直到下一个标签不同于它,找到数组之后依次将这些值插入数组对象,并将整个数组对象插入到json对象中。
当它只是一个普通的对象时会根据是否存在引号来判断它是否是字符串,然后调用不同的添加项的函数来插入数据
最后将json对象转化为字符串,清空内存并返回函数(万别忘记清理内存
整个项目的下载地址:下载

原创粉丝点击