之前完成了一個兩個平台對接的項目。由於這兩個平台一個是使用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對象轉化為字符串,清空內存並返回函數(萬別忘記清理內存)
整個項目的下載地址:下載
