前言
JSON(JavaScript Object Notation)是一種輕量級的資料交換格式。JSON在網際網路相關開發中用得很多,在我們嵌入式中用得也不少。最近在專案中就有用到,用起來很方便。
簡單的JSON格式資料如:
{ "name": "xxx", "num": xxx, "c_score": xxx}
這裡我們需要知道一個概念:鍵值對。比如:
"name": "xxx"
像這樣子的就是一對鍵值對。
當我們作為傳送方時,我們要把xxx這些有用的資料組合成JSON格式的資料傳送給接收方;當我們作為接收方時,我們需要從這一堆JSON資料中解析出xxx這些有用的資料拿來使用。簡單的JSON資料,我們使用C語言的一些字串操作相關的庫函式也是可以做到組包和解析的,但是一些稍微複雜一點的JSON,可能就沒那麼好操作了。
這時候我們可以藉助一個第三方庫——cJSON庫,可以很方便來做資料組包及解析。
cJSON庫倉庫地址:
https://github.com/DaveGamble/cJSON.git
或者
https://gitee.com/mirrors/cJSON.git
下面我們透過例項來分享使用cJSON庫來做資料組包及資料解析。
組包、解析示例1、確定協議資料在實際開發中,要把JSON資料作為通訊的資料,自然要先確定通訊雙方要互動的資料有哪些,如有需要還需編寫形成協議文件。協議文件包含要傳輸的資料,資料型別等資訊。比如:
2、組JSON資料包示例從控制檯輸入一些學生資訊,組合成字串格式的JSON資料包,然後再輸出至控制檯。
操作示例:首先,我們先從倉庫下載cJSON原始碼,資料夾內容如:
我們只需要把cJSON.c、cJSON.h兩個檔案複製到我們工程的根目錄下就可以使用,如:
從cJSON.h可以看到其給我們提供了很多介面:
本例中我們重點關注如下幾個介面即可:
cJSON_CreateObject:建立JSON物件,{}擴起來的cJSON_CreateString:建立字串cJSON_CreateNumber:建立int型別資料cJSON_AddItemToObject:新增到JSON物件中cJSON_Print:呈現為標準的JSON格式cJSON_PrintUnformatted:呈現為去掉空格的JSON格式cJSON_Delete:JSON物件刪除,做一些釋放記憶體的工作
我們建立的的組包函式如下:
static char *StudentsData_Packet(pStudentDef _Stu){ char *res_string = NULL; // 返回值 cJSON *name = NULL; // 名字 cJSON *num = NULL; // 學號 cJSON *c_score = NULL; // C語言分數 /* 建立一個JSON物件,{}擴起來 */ cJSON *obj = cJSON_CreateObject(); if (obj == NULL) { goto end; } /* 建立 "name": "xxx" 鍵值對 */ name = cJSON_CreateString(_Stu->name); if (name == NULL) { goto end; } cJSON_AddItemToObject(obj, "name", name); /* 建立 "num": 207 鍵值對 */ num = cJSON_CreateNumber(_Stu->num); if (name == NULL) { goto end; } cJSON_AddItemToObject(obj, "num", num); /* 建立 "c_score": 95 鍵值對 */ c_score = cJSON_CreateNumber(_Stu->c_score); if (name == NULL) { goto end; } cJSON_AddItemToObject(obj, "c_score", c_score); res_string = cJSON_Print(obj); // 呈現為JSON格式 // res_string = cJSON_PrintUnformatted(obj); // 呈現為無格式 if (res_string == NULL) { fprintf(stderr, "Failed to print monitor.\n"); }/* 異常情況統一Delete(free) */end: cJSON_Delete(obj); return res_string;}
詳細解釋見註釋。我們重點看一下cJSON_Print與cJSON_PrintUnformatted這兩個介面。這兩個介面的差別就是組合成的JSON資料是否有空格。我們透過JSON相關的線上網站看一下其區別:
https://www.sojson.com/json/json_online.html
有空格的JSON資料,即用cJSON_Print時的效果為:
無空格的JSON資料,即用cJSON_PrintUnformatted時的效果為:
如果想要輸出檢視時,當然是用cJSON_Print比較方便檢視;如果是實際通訊時,當然是用cJSON_PrintUnformatted會比較好,畢竟去掉空格就可以減小一定程度的通訊負擔。
student_data.txt的內容如:
解析結果:
關於這個示例我們需要關注的介面有:
cJSON_Parse:JSON解析函式,解析{}得到裡面的內容cJSON_GetObjectItemCaseSensitive:從物件中獲取鍵“字串”。不分大小寫cJSON_IsString:判斷是否是字串cJSON_IsNumber:判斷是否是整形數cJSON_Delete:JSON物件刪除,做一些釋放記憶體的工作
我們建立的解析函式如下:
static void StudentsData_Parse(pStudentDef _Stu, const char *_JsonStudnetData){ cJSON *student_json = NULL; // student_json操作物件,可代表 {} 擴起來的內容 cJSON *name = NULL; cJSON *num = NULL; cJSON *c_score = NULL; /* 開始解析 */ student_json = cJSON_Parse(_JsonStudnetData); if (NULL == student_json) { const char *error_ptr = cJSON_GetErrorPtr(); if (error_ptr != NULL) { fprintf(stderr, "Error before: %s\n", error_ptr); } goto end; } /* 解析獲取name得值 */ name = cJSON_GetObjectItemCaseSensitive(student_json, "name"); if (cJSON_IsString(name) && (name->valuestring != NULL)) { memcpy(&_Stu->name, name->valuestring, strlen(name->valuestring)); } /* 解析獲取num的值 */ num = cJSON_GetObjectItemCaseSensitive(student_json, "num"); if (cJSON_IsNumber(num)) { _Stu->num = num->valueint; } /* 解析獲取c_score的值 */ c_score = cJSON_GetObjectItemCaseSensitive(student_json, "c_score"); if (cJSON_IsNumber(c_score)) { _Stu->c_score = c_score->valueint; }end: cJSON_Delete(student_json);}
解釋見註釋。
執行演示:
gcc json_print.c cJSON.c -o json_print.exe -lwsocket32gcc json_parse.c cJSON.c -o json_parse.exe -lwsocket32
綜合demo加了socket相關程式碼,本篇筆記主要介紹JSON資料的組包及解析。關於socket相關的內容不做過多解釋。感興趣的朋友可閱讀往期socket相關的筆記: