C语言与JSON的优雅邂逅:如何在C中解析与生成JSON
在当今的软件开发中,JSON(JavaScript Object Notation)已经成为数据交换的事实标准,它轻量、易读且易于机器解析和生成,对于许多C语言开发者来说,如何处理这种灵活的数据结构一直是一个挑战,C语言本身不提供内置的JSON支持,因此我们需要借助第三方库来“搭起”C语言与JSON之间的桥梁,本文将详细介绍如何在C语言中使用流行的第三方库来解析和生成JSON,实现数据的“分开”与重组。
核心思想:将JSON映射为C语言数据结构
在C语言中处理JSON,其核心思想是将JSON文本中描述的数据结构,解析(Parsing)成C语言原生能够理解的数据结构(如结构体struct和联合体union),反之亦然,这个过程就是“分开”与“重组”的本质。
- 解析:将一整段JSON字符串,分解成一个个独立的、有意义的C语言数据项。
- 生成:将C语言中的数据项,组合成一段符合JSON规范的字符串。
为了实现这个过程,我们通常会选择一个成熟的C语言JSON库,目前市面上最流行、功能最强大的库之一是 JSMN (JavaScript Object Notation Parser),特别是它的改进版 JSMN-EX,还有 cJSON,它提供了更高级的API,使用起来更为便捷,下面,我们将分别介绍这两个库的使用方法。
使用JSMN-EX库——轻量级的选择
JSMN以其极小(核心代码仅约200行)和快速而闻名,它不直接将JSON映射为C结构体,而是生成一个“令牌”(Token)流,开发者需要根据这个令牌流手动解析出数据,这种方式非常灵活,但需要开发者做更多的工作。
安装与准备
从GitHub上克隆JSMN-EX项目:
git clone https://github.com/zserge/jsmn.git
你只需要将 jsmn.h 和 jsmn.c 文件包含到你的项目中即可。
解析JSON
JSMN的解析过程分为两步:分配内存和实际解析。
示例:解析一个简单的JSON对象
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "jsmn.h"
// 一个示例JSON字符串
const char *JSON_STRING = "{ \"name\": \"John\", \"age\": 30, \"isStudent\": false, \"courses\": [\"Math\", \"Science\"] }";
int main() {
int r;
jsmn_parser p;
jsmn_tok_t t[128]; // 我们需要一个令牌数组来存储解析结果
// 初始化解析器
jsmn_init(&p);
// 解析JSON字符串
// JSMN_OBJECT表示我们期望解析一个JSON对象
r = jsmn_parse(&p, JSON_STRING, strlen(JSON_STRING), t, sizeof(t) / sizeof(t[0]));
if (r < 0) {
printf("Failed to parse JSON: %d\n", r);
return 1;
}
// 检查是否是JSON对象
if (r < 1 || t[0].type != JSMN_OBJECT) {
printf("Object expected\n");
return 1;
}
// 遍历令牌,提取数据
for (int i = 1; i < r; i++) {
jsmn_tok_t *token = &t[i];
if (token->type == JSMN_STRING) {
// 如果是字符串键或值
const char *key = JSON_STRING + t[i-1].start;
const char *val = JSON_STRING + token->start;
int len = token->end - token->start;
if (strncmp(key, "\"name\"", t[i-1].end - t[i-1].start) == 0) {
printf("Name: %.*s\n", len, val);
} else if (strncmp(key, "\"age\"", t[i-1].end - t[i-1].start) == 0) {
// 年龄是数字,但在这里被解析为字符串,需要额外处理
printf("Age: %.*s\n", len, val);
} else if (strncmp(key, "\"isStudent\"", t[i-1].end - t[i-1].start) == 0) {
// 布尔值同理
printf("IsStudent: %.*s\n", len, val);
}
} else if (token->type == JSMN_ARRAY) {
// 处理数组
printf("Courses: [");
for (int j = i + 1; j < i + 1 + token->size; j++) {
const char *course = JSON_STRING + t[j].start;
printf("%.*s", t[j].end - t[j].start, course);
if (j < i + token->size) printf(", ");
}
printf("]\n");
// 跳过已处理的数组元素
i += token->size;
}
}
return 0;
}
分析:
- JSMN不创建任何动态内存分配,非常安全。
- 它返回一个
jsmntok_t数组,每个token描述了JSON中的一个元素(对象、数组、字符串、数字等)及其在原始字符串中的位置。 - 开发者需要手动遍历这些token,并根据它们的类型和上下文来提取和转换数据,这就是“分开”的过程。
使用cJSON库——功能强大的选择
cJSON库提供了更高级的API,它会自动将JSON数据解析成树状结构,每个节点都是一个cJSON对象,开发者可以像操作链表或树一样方便地访问数据,无需关心底层token。
安装与准备
从GitHub克隆项目:
git clone https://github.com/DaveGamble/cJSON.git
编译并安装,或者直接将 cJSON.h 和 cJSON.c 加入你的项目。
解析JSON
cJSON的解析非常直观。
示例:解析同样的JSON对象
#include <stdio.h>
#include <stdlib.h>
#include "cJSON.h"
int main() {
const char *JSON_STRING = "{ \"name\": \"John\", \"age\": 30, \"isStudent\": false, \"courses\": [\"Math\", \"Science\"] }";
// 1. 解析JSON字符串,得到cJSON对象(根节点)
cJSON *root = cJSON_Parse(JSON_STRING);
if (root == NULL) {
const char *error_ptr = cJSON_GetErrorPtr();
if (error_ptr != NULL) {
fprintf(stderr, "Error before: %s\n", error_ptr);
}
return 1;
}
// 2. 从根节点获取各个字段的值
cJSON *name_item = cJSON_GetObjectItemCaseSensitive(root, "name");
if (cJSON_IsString(name_item) && (name_item->valuestring != NULL)) {
printf("Name: %s\n", name_item->valuestring);
}
cJSON *age_item = cJSON_GetObjectItemCaseSensitive(root, "age");
if (cJSON_IsNumber(age_item)) {
printf("Age: %d\n", age_item->valueint); // 使用valueint获取整数
}
cJSON *is_student_item = cJSON_GetObjectItemCaseSensitive(root, "isStudent");
if (cJSON_IsBool(is_student_item)) {
printf("IsStudent: %s\n", cJSON_IsTrue(is_student_item) ? "true" : "false");
}
cJSON *courses_item = cJSON_GetObjectItemCaseSensitive(root, "courses");
if (cJSON_IsArray(courses_item)) {
int course_count = cJSON_GetArraySize(courses_item);
printf("Courses: [");
for (int i = 0; i < course_count; i++) {
cJSON *course = cJSON_GetArrayItem(courses_item, i);
printf("\"%s\"", course->valuestring);
if (i < course_count - 1) {
printf(", ");
}
}
printf("]\n");
}
// 3. 释放内存(非常重要!)
cJSON_Delete(root);
return 0;
}
分析:
cJSON_Parse()将整个JSON字符串转换为一个cJSON对象树。cJSON_GetObjectItemCaseSensitive()通过键名直接获取子节点,非常方便。cJSON_IsString(),cJSON_IsNumber()等宏用于检查节点类型,确保安全访问。cJSON_GetArrayItem()用于访问数组元素。- 必须调用
cJSON_Delete(root)来释放整个JSON树占用的内存,否则会造成内存泄漏。
生成JSON
cJSON不仅能解析,还能轻松生成JSON。
#include <stdio.h> #include



还没有评论,来说两句吧...