1. libjson-c库基础解析
在C语言项目中处理JSON数据时,libjson-c是一个轻量级且高效的选择。这个开源库提供了完整的JSON解析和生成功能,特别适合嵌入式系统和资源受限环境。我最近在几个物联网设备通信项目中深度使用了这个库,积累了一些实战经验。
首先需要明确的是,libjson-c采用引用计数机制管理内存。每个json_object创建时引用计数为1,当调用json_object_put()时计数减1。这个设计看似简单,但有几个关键细节需要注意:
- 临时变量不需要手动释放,它们属于"借用引用"
- 通过json_object_object_add()添加的子对象会被父对象接管引用
- 数组和对象中的元素引用由容器统一管理
安装libjson-c非常简单,主流Linux发行版都提供包管理支持:
bash复制# Ubuntu/Debian
sudo apt-get install libjson-c-dev
# CentOS/RHEL
sudo yum install json-c-devel
基础使用需要包含头文件:
c复制#include <json-c/json.h>
#include <stdio.h> // 用于调试输出
2. JSON解析核心API详解
2.1 字符串解析与类型判断
解析JSON字符串是使用频率最高的功能,核心函数是json_tokener_parse():
c复制const char *json_str = "{\"name\":\"John\", \"age\":30}";
struct json_object *root = json_tokener_parse(json_str);
if (!root) {
fprintf(stderr, "JSON解析失败\n");
return -1;
}
类型判断在实际开发中至关重要,libjson-c提供了两种方式:
c复制// 方法1:直接类型判断
if (json_object_is_type(root, json_type_object)) {
printf("这是一个JSON对象\n");
}
// 方法2:获取类型后再判断
enum json_type type = json_object_get_type(root);
switch(type) {
case json_type_null: /* 处理null */ break;
case json_type_boolean: /* 处理布尔值 */ break;
// ...其他类型处理
}
2.2 键值提取与安全操作
提取特定键值时,推荐使用json_object_object_get_ex()而非直接访问,因为它提供了NULL安全检查:
c复制struct json_object *name_obj;
if (json_object_object_get_ex(root, "name", &name_obj)) {
const char *name = json_object_get_string(name_obj);
printf("姓名: %s\n", name);
// 重要:不要直接保存指针,应该复制值
char name_copy[256];
strncpy(name_copy, name, sizeof(name_copy)-1);
}
对于数值类型,库提供了多种获取方式:
c复制int age = json_object_get_int(json_object_object_get(root, "age"));
double score = json_object_get_double(json_object_object_get(root, "score"));
特别注意:json_object_get_string()返回的指针在对象释放后无效,必须立即复制值
3. 复杂JSON结构处理
3.1 数组类型解析实战
处理JSON数组是常见需求,比如解析设备传感器数据:
c复制int parse_sensor_data(const json_object *root) {
struct json_object *sensors;
if (!json_object_object_get_ex(root, "sensors", &sensors)) {
return -1;
}
int array_len = json_object_array_length(sensors);
for (int i = 0; i < array_len; i++) {
struct json_object *elem = json_object_array_get_idx(sensors, i);
if (elem) {
struct json_object *sensor_id, *value;
json_object_object_get_ex(elem, "id", &sensor_id);
json_object_object_get_ex(elem, "value", &value);
printf("传感器%d: 值=%.2f\n",
json_object_get_int(sensor_id),
json_object_get_double(value));
}
}
return 0;
}
3.2 嵌套对象深度遍历
对于多层嵌套的JSON结构,可以采用递归方式处理:
c复制void traverse_json(json_object *obj) {
switch(json_object_get_type(obj)) {
case json_type_object: {
json_object_object_foreach(obj, key, val) {
printf("键: %s => ", key);
traverse_json(val);
}
break;
}
case json_type_array: {
int len = json_object_array_length(obj);
for (int i = 0; i < len; i++) {
traverse_json(json_object_array_get_idx(obj, i));
}
break;
}
// 基本类型处理...
}
}
4. JSON生成与内存管理
4.1 构建复杂JSON对象
创建包含嵌套结构的JSON数据:
c复制json_object *create_device_status() {
json_object *root = json_object_new_object();
// 添加基本字段
json_object_object_add(root, "device_id", json_object_new_string("D-12345"));
json_object_object_add(root, "online", json_object_new_boolean(1));
// 构建嵌套的GPS数据
json_object *gps = json_object_new_object();
json_object_object_add(gps, "latitude", json_object_new_double(39.9042));
json_object_object_add(gps, "longitude", json_object_new_double(116.4074));
json_object_object_add(root, "gps", gps);
// 添加传感器数组
json_object *sensors = json_object_new_array();
for (int i = 0; i < 3; i++) {
json_object *sensor = json_object_new_object();
json_object_object_add(sensor, "type", json_object_new_string("temp"));
json_object_object_add(sensor, "value", json_object_new_double(25.5 + i));
json_object_array_add(sensors, sensor);
}
json_object_object_add(root, "sensors", sensors);
return root;
}
4.2 内存管理最佳实践
libjson-c的内存管理看似简单,但有几个关键点需要注意:
-
引用传递规则:
- json_object_object_add()会转移对象所有权
- json_object_array_add()同样会转移所有权
- json_object_get()会增加引用计数
-
典型内存泄漏场景:
c复制// 错误示例:创建后未释放
json_object *obj = json_object_new_string("leak");
// 忘记调用 json_object_put(obj);
// 错误示例:重复释放
json_object_put(obj);
json_object_put(obj); // 崩溃!
- 安全释放模式:
c复制void process_json(const char *json_str) {
json_object *root = json_tokener_parse(json_str);
if (!root) return;
// 处理JSON内容...
// 释放整个树
json_object_put(root); // 会自动递归释放所有子对象
}
5. 实战技巧与常见问题
5.1 性能优化建议
- 避免频繁解析:对于重复使用的JSON,解析一次后缓存json_object
- 使用静态缓冲区:对于已知大小的字符串,避免动态内存分配
- 批量操作:修改多个字段时,集中操作减少锁开销
c复制// 优化示例:批量添加字段
void add_device_info(json_object *root) {
json_object *info = json_object_new_object();
json_object_object_add(info, "model", json_object_new_string("X100"));
json_object_object_add(info, "version", json_object_new_string("1.2.3"));
json_object_object_add(root, "device_info", info);
}
5.2 错误处理模式
健壮的JSON处理需要完善的错误检查:
c复制int parse_config(const char *config_str) {
json_object *root = json_tokener_parse(config_str);
if (!root) {
fprintf(stderr, "无效的JSON格式\n");
return -1;
}
json_object *timeout;
if (!json_object_object_get_ex(root, "timeout", &timeout) ||
!json_object_is_type(timeout, json_type_int)) {
fprintf(stderr, "缺少或无效的timeout参数\n");
json_object_put(root);
return -1;
}
int timeout_val = json_object_get_int(timeout);
json_object_put(root);
return timeout_val;
}
5.3 调试与问题排查
- 打印完整JSON:
c复制printf("当前JSON内容:\n%s\n", json_object_to_json_string_ext(root, JSON_C_TO_STRING_PRETTY));
- 类型不匹配:使用json_type_to_name()打印类型信息
c复制printf("字段类型: %s\n", json_type_to_name(json_object_get_type(obj)));
- 内存问题排查:
- 使用valgrind检查内存泄漏
- 确保每个json_object_new_*都有对应的json_object_put
6. 高级应用场景
6.1 自定义序列化配置
json_object_to_json_string_ext()支持多种输出格式:
c复制// 紧凑格式(无空格)
const char *compact = json_object_to_json_string_ext(root, JSON_C_TO_STRING_PLAIN);
// 带缩进的易读格式
const char *pretty = json_object_to_json_string_ext(root, JSON_C_TO_STRING_PRETTY);
// 避免UTF-8转义
const char *noescape = json_object_to_json_string_ext(root, JSON_C_TO_STRING_NOSLASHESCAPE);
6.2 流式解析处理
对于大文件,可以采用分段解析:
c复制struct json_tokener *tok = json_tokener_new();
json_object *obj = NULL;
while ((obj = json_tokener_parse_ex(tok, buffer, len)) == NULL) {
// 需要更多数据...
if (json_tokener_get_error(tok) != json_tokener_continue)
break;
}
if (obj) {
// 处理完整对象
json_object_put(obj);
}
json_tokener_free(tok);
6.3 与其他库的集成
当需要与其他数据格式转换时:
c复制// JSON转YAML示例
#include <yaml.h>
void json_to_yaml(json_object *root) {
yaml_emitter_t emitter;
yaml_emitter_initialize(&emitter);
// ...转换逻辑
}
在实际项目中使用libjson-c时,我发现最实用的经验是:始终为每个JSON操作编写配套的错误检查,特别是在处理网络传输或用户输入的JSON数据时。曾经因为一个缺少的null检查导致服务崩溃,这个教训让我养成了防御性编程的习惯。
