一、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中源码如下
cpp
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.创建头指针
cpp
cJSON* cjson_test = NULL;
2.创建头结点,并将头指针指向头结点
cpp
cjson_test = cJSON_CreateObject();
3.调用函数向链表中插入结点
cpp
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数据
cpp
(char *) cJSON_Print(const cJSON *item);
一段完整的JSON数据就是一条长长的链表,cJSON提供了一个API,可以将整条链表中存放的JSON信息输出到一个字符串中,使用的时候,只需要接收该函数返回的指针地址即可。
3.4cJSON数据解析(含源码分析)
数据解析调用cJSON_Parse(const char *value)
解析JSON数据的过程,其实就是剥离一个一个链表节点(键值对)的过程
cpp
// 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给出代码示例封装及解析
嵌套的数据封装时递归封装解析时也递归解析
cpp
#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", "[email protected]");
// 创建 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 添加到 user
cJSON_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;
}
// 打印生成的 JSON
printf("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
进行字符串比较,即可定位目标省份。
代码:
cpp
#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 更简洁,解析效率更高