一、JSON介紹
JSON(JavaScript Object Notation 即JavaScript對象表示法)是一種輕量級的數據交換格式。采用完全獨立于編程語言的文本格式來存儲和表示數據。
- JSON是一種數據交換格式.
- JSON獨立于編程語言(你不必學習JavaScript).
- JSON表達數據的方式對通用的編程概念都很友好.
本文主要介紹使用C語言解析JSON文件
二、JSON語法規則
數據結構
- 對象(Object):使用花括號
{}
包裹,由鍵值對組成。鍵必須是字符串,使用雙引號""
包裹,鍵值對之間用逗號,
分隔。 - 數組(Array):使用方括號
[]
包裹,值之間用逗號,
分隔。
值的類型
- 字符串(String):使用雙引號
""
包裹的 Unicode 字符序列。 - 數值(Number):整數或浮點數,不使用引號,數字沒有長度限制。
- 布爾值(Boolean):
true
或false
,表示"有"或者"沒有",不使用引號。 - 空值(Null):
null
,表示沒有這項內容,它不是0,0是一個數字,本質上是在計數,不使用引號。 - 嵌套對象或數組:可在值中嵌套對象或數組。數組:是由方括號括起來的一組值構成
注意事項
- 不允許使用單引號。
- 不允許包含注釋。
- 不允許使用未定義的變量或特殊符號(如
NaN
、Infinity
)。 - 數組和對象可以包含不同類型的值。
- 使用冒號:來分割名稱和值,名稱始終在左側,一定是字符串,值始終在右側可以是多種類型
三、cJSON
3.1cJSON介紹和獲取
首先在GitHub上下載開源的cjson.h以及cjson.c文件
這里給出附件
使用的時候,只需要將這兩個文件復制到工程目錄,然后包含頭文件cJSON.h即可,如下:
#include "cJSON.h"
3.2cJSON數據結構和設計思想
核心數據結構:cJSON
?結構體
cJSON 使用單一結構體表示所有 JSON 類型,通過?type
?字段區分不同類型。
cJSON.h中源碼如下
typedef struct cJSON {struct cJSON *next,*prev; /* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */struct cJSON *child; /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */int type; /* The type of the item, as above. */char *valuestring; /* The item's string, if type==cJSON_String */int valueint; /* The item's number, if type==cJSON_Number */double valuedouble; /* The item's number, if type==cJSON_Number */char *string; /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */
} cJSON;
統一表示所有 JSON 類型:將其中的一條JSON數據抽象出來,也就是一個鍵值對,用上面的結構體 strcut cJSON 來表示
- 通過?
type
?字段(如?JSON_Object
,?JSON_Array
,?JSON_Number
)區分不同類型。 - 數值類型通過?
valueint
?和?valuedouble
?存儲,字符串類型通過?valuestring
?存儲。
雙向鏈表管理數組和對象成員:
- 數組(
JSON_Array
)和對象(JSON_Object
)使用雙向鏈表?next
?和?prev
?連接成員。 - 對象的鍵值對通過?
child
?指針指向第一個成員,每個成員的?string
?字段存儲鍵名
遞歸嵌套結構:
- 子節點可以是任意 JSON 類型,支持無限層級嵌套(如數組包含對象,對象包含數組)。
設計思想
1.輕量級與可移植性
- 無依賴:僅依賴標準 C 庫(
stdlib.h
,?string.h
,?math.h
),無需額外依賴。 - 單文件實現:核心代碼集中在?
cJSON.c
?和?cJSON.h
,便于集成到嵌入式系統或資源受限環境。
2. 簡單易用的 API
提供直觀的函數接口,例如:
cJSON_Parse()
:解析 JSON 字符串為?cJSON
?對象。cJSON_GetObjectItem()
:通過鍵名獲取對象成員。cJSON_Print()
:將?cJSON
?對象序列化為 JSON 字符串。cJSON_CreateObject()
/cJSON_CreateArray()
:創建 JSON 對象 / 數組。
3. 動態內存管理
- 使用?
malloc()
?和?free()
?動態分配和釋放內存,需手動調用?cJSON_Delete()
?釋放不再使用的對象。 - 支持自定義內存分配函數(通過?
cJSON_Hooks
?結構體),適配不同環境。
4. 錯誤處理
- 解析失敗時返回?
NULL
,通過?cJSON_GetErrorPtr()
?獲取錯誤位置。 - 不提供詳細錯誤信息(如行號、具體錯誤類型),適合快速解析簡單 JSON。
3.3cJSON數據封裝(含源碼分析)
封裝JSON數據的過程,其實就是創建鏈表和向鏈表中添加節點的過程。
1.創建頭指針
cJSON* cjson_test = NULL;
2.創建頭結點,并將頭指針指向頭結點
cjson_test = cJSON_CreateObject();
3.調用函數向鏈表中插入結點
cJSON_AddNullToObject(cJSON * const object, const char * const name);cJSON_AddTrueToObject(cJSON * const object, const char * const name);cJSON_AddFalseToObject(cJSON * const object, const char * const name);cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean);cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number);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數據
(char *) cJSON_Print(const cJSON *item);
一段完整的JSON數據就是一條長長的鏈表,cJSON提供了一個API,可以將整條鏈表中存放的JSON信息輸出到一個字符串中,使用的時候,只需要接收該函數返回的指針地址即可。
3.4cJSON數據解析(含源碼分析)
數據解析調用cJSON_Parse(const char *value)
解析JSON數據的過程,其實就是剝離一個一個鏈表節點(鍵值對)的過程
// 1.創建鏈表頭指針:
cJSON* cjson_test = NULL;
//2.解析整段JSON數據,并將鏈表頭結點地址返回,賦值給頭指針:解析整段數據使用的API只有一個:
(cJSON *) cJSON_Parse(const char *value);
//3.根據鍵值對的名稱從鏈表中取出對應的值,返回該鍵值對(鏈表節點)的地址
(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string);
//4.如果JSON數據的值是數組,使用下面的兩個API提取數據:
(int) cJSON_GetArraySize(const cJSON *array);
(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index);
3.5cJSON內存管理
cJSON的所有操作都是基于鏈表的,cJSON在使用過程中大量的使用malloc從堆中分配動態內存的,所以在使用完之后,應當及時調用下面的函數,清空cJSON指針所指向的內存,該函數也可用于刪除某一條數據:
void cJSON_Delete(cJSON *item);
3.6給出代碼示例封裝及解析
嵌套的數據封裝時遞歸封裝解析時也遞歸解析
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "cJSON.h"// 封裝(生成)JSON 數據
char* encapsulate_json_data() {// 創建根對象cJSON *root = cJSON_CreateObject();// 創建 user 對象cJSON *user = cJSON_CreateObject();cJSON_AddNumberToObject(user, "id", 12345);cJSON_AddStringToObject(user, "name", "Alice");// 創建 contact 對象cJSON *contact = cJSON_CreateObject();cJSON_AddStringToObject(contact, "email", "alice@example.com");// 創建 phone 數組cJSON *phone = cJSON_CreateArray();cJSON_AddItemToArray(phone, cJSON_CreateString("123-456-7890"));cJSON_AddItemToArray(phone, cJSON_CreateString("098-765-4321"));cJSON_AddItemToObject(contact, "phone", phone);// 將 contact 添加到 usercJSON_AddItemToObject(user, "contact", contact);// 將 user 添加到根對象cJSON_AddItemToObject(root, "user", user);// 添加布爾值cJSON_AddBoolToObject(root, "isActive", 1); // 1 表示 true// 添加嵌套數組cJSON *nested_array = cJSON_CreateArray();cJSON_AddItemToArray(nested_array, cJSON_CreateNumber(100));cJSON_AddItemToArray(nested_array, cJSON_CreateString("nested value"));cJSON *nested_obj = cJSON_CreateObject();cJSON_AddStringToObject(nested_obj, "key", "value");cJSON_AddItemToArray(nested_array, nested_obj);cJSON_AddItemToObject(root, "nested", nested_array);// 生成 JSON 字符串char *json_str = cJSON_Print(root);// 釋放 JSON 對象cJSON_Delete(root);return json_str;
}// 解析 JSON 數據
void parse_json_data(const char *json_str) {// 解析 JSON 字符串cJSON *root = cJSON_Parse(json_str);if (root == NULL) {const char *error_ptr = cJSON_GetErrorPtr();if (error_ptr != NULL) {fprintf(stderr, "Error before: %s\n", error_ptr);}return;}// 提取基本字段cJSON *isActive = cJSON_GetObjectItem(root, "isActive");if (cJSON_IsBool(isActive)) {printf("isActive: %s\n", isActive->valueint ? "true" : "false");}// 提取 user 對象cJSON *user = cJSON_GetObjectItem(root, "user");if (cJSON_IsObject(user)) {cJSON *id = cJSON_GetObjectItem(user, "id");cJSON *name = cJSON_GetObjectItem(user, "name");if (cJSON_IsNumber(id) && cJSON_IsString(name)) {printf("User ID: %d\n", id->valueint);printf("User Name: %s\n", name->valuestring);}// 提取 contact 對象cJSON *contact = cJSON_GetObjectItem(user, "contact");if (cJSON_IsObject(contact)) {cJSON *email = cJSON_GetObjectItem(contact, "email");if (cJSON_IsString(email)) {printf("Email: %s\n", email->valuestring);}// 提取 phone 數組cJSON *phone = cJSON_GetObjectItem(contact, "phone");if (cJSON_IsArray(phone)) {int array_size = cJSON_GetArraySize(phone);printf("Phone Numbers (%d):\n", array_size);for (int i = 0; i < array_size; i++) {cJSON *phone_item = cJSON_GetArrayItem(phone, i);if (cJSON_IsString(phone_item)) {printf(" %d: %s\n", i+1, phone_item->valuestring);}}}}}// 解析嵌套數組cJSON *nested = cJSON_GetObjectItem(root, "nested");if (cJSON_IsArray(nested)) {printf("Nested Array:\n");int array_size = cJSON_GetArraySize(nested);for (int i = 0; i < array_size; i++) {cJSON *item = cJSON_GetArrayItem(nested, i);printf(" Item %d: ", i+1);if (cJSON_IsNumber(item)) {printf("Number = %f\n", item->valuedouble);} else if (cJSON_IsString(item)) {printf("String = %s\n", item->valuestring);} else if (cJSON_IsObject(item)) {printf("Object\n");cJSON *key = cJSON_GetObjectItem(item, "key");if (cJSON_IsString(key)) {printf(" key = %s\n", key->valuestring);}}}}// 釋放 JSON 對象cJSON_Delete(root);
}int main() {// 封裝 JSON 數據char *json_str = encapsulate_json_data();if (json_str == NULL) {fprintf(stderr, "Failed to generate JSON data.\n");return 1;}// 打印生成的 JSONprintf("Generated JSON:\n%s\n\n", json_str);// 解析 JSON 數據printf("Parsing JSON data...\n");parse_json_data(json_str);// 釋放 JSON 字符串free(json_str);return 0;
}
四、cJSON項目
這個項目實現了一個使用 C 語言解析 JSON 文件的應用程序,主要內容是從包含中國行政區劃信息的 JSON 文件中提取特定省份的城市列表。
具體實現思路:
因為cJSON.h中并沒有定義對文件的操作,所以通過使用標準 C 庫函數?fopen
、fread
?和?fseek
?讀取 JSON 文件內容,把文件內容讀取到字符串中,然后解析字符串,注意使用動態分配內存存儲 JSON 數據,并在使用后正確釋放,實際操作直接使用 cJSON 庫解析 JSON 數據結構,包括對象、數組的遍歷,最后通過使用?strcmp
?進行字符串比較,即可定位目標省份。
代碼:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "cJSON.h"char* read_file(const char* filename) {FILE* f = fopen(filename, "rb");if (!f) {perror("無法打開文件");return NULL;}fseek(f, 0, SEEK_END);long size = ftell(f);fseek(f, 0, SEEK_SET);char* buff = (char*)malloc(size + 1);if (buff == NULL) {perror("創建內存失敗");fclose(f);return NULL;}size_t bytes_read = fread(buff, 1, size, f);//目標緩沖區 buff,每個數據項的大小(這里是 1 字節),要讀取的數據項數量 size,以及文件流 f。if (bytes_read < size) {if (feof(f)) {//feof 函數用于檢查文件流 f 是否已經到達文件末尾。printf("警告:文件已到達末尾,讀取 %zu 字節(期望 %ld 字節)\n", bytes_read, size);}else if (ferror(f)) {perror("讀取錯誤");free(buff);fclose(f);return NULL;}}buff[bytes_read] = '\0';fclose(f);return buff;
}int main() {char* json_str = read_file("city.json");if (json_str == NULL) {return -1;}cJSON* root = cJSON_Parse(json_str);if (root == NULL) {const char* error_ptr = cJSON_GetErrorPtr();if (error_ptr != NULL) {fprintf(stderr, "JSON解析錯誤: %s\n", error_ptr);}free(json_str);return -1;}// 檢查 root 是否為數組if (root->type != cJSON_Array) {printf("JSON 根節點不是數組類型\n");cJSON_Delete(root);free(json_str);return -1;}const char* target_province = "陜西";int province_found = 0;// 遍歷所有省份int province_count = cJSON_GetArraySize(root);for (int i = 0; i < province_count; i++) {cJSON* province_item = cJSON_GetArrayItem(root, i);if (!province_item) continue;// 獲取省份名稱cJSON* name = cJSON_GetObjectItem(province_item, "label");if (!name || name->type != cJSON_String) continue;// 檢查是否為目標省份if (strcmp(name->valuestring, target_province) == 0) {printf("找到省份:%s\n", name->valuestring);province_found = 1;// 獲取城市數組cJSON* cities_array = cJSON_GetObjectItem(province_item, "children");//注意數據嵌套關系,城市在省份的children數組里if (cities_array && cities_array->type == cJSON_Array) {int city_count = cJSON_GetArraySize(cities_array);printf("包含 %d 個城市:\n", city_count);for (int j = 0; j < city_count; j++) {cJSON* city_item = cJSON_GetArrayItem(cities_array, j);if (!city_item) continue;cJSON* city_name = cJSON_GetObjectItem(city_item, "label");if (city_name && city_name->type == cJSON_String) {printf(" %d. %s\n", j + 1, city_name->valuestring);}}}else {printf("未找到該省份的城市數據\n");}break; // 找到目標省份后退出循環}}if (!province_found) {printf("未找到省份:%s\n", target_province);}cJSON_Delete(root);free(json_str);return 0;
}
運行結果:
這里以查找陜西省的城市為例:
五、 JSON的應用場景有哪些?
多個應用場景:
-
Web API 數據傳輸
- RESTful API 的請求和響應格式
- 前后端數據交互(如 AJAX 請求)
-
配置文件
- 應用程序配置(如 Node.js 的 package.json)
- 開發工具配置(如 ESLint、Babel)
-
數據存儲
- NoSQL 數據庫(如 MongoDB)存儲文檔
- 臨時數據緩存(如 Redis)
-
跨語言數據交換
- 微服務間通信(如通過消息隊列)
- 不同系統間數據集成
-
移動應用開發
- 與服務器交換數據(如 JSON API)
- 本地存儲(如 React Native 的 AsyncStorage)
-
日志和監控系統
- 結構化日志輸出
- 監控指標傳輸
-
前端框架
- React/Vue 組件屬性傳遞
- 路由配置
優勢總結:
- 輕量級,易于閱讀和編寫
- 支持嵌套結構,適合復雜數據
- 幾乎所有編程語言都有解析庫
- 與 JavaScript 原生兼容
- 比 XML 更簡潔,解析效率更高