图片

正文

大家好,我是bug菌,又又见面了~

今天跟大家聊聊json这玩意,json说实在的bug菌也是后面联网的项目越来越多才用上了这玩意,玩物联网和智能硬件的朋友应该用得比较多。这不最近一个"小项目"又把这玩意拿过来用用,整体还是挺舒适的,主要是json易读也比较容易把握,那顺便聊聊吧~

1

cjson

在资源受限的单片机上解析JSON,开发者常面临内存不足、解析性能差等问题,所以在单片机上用cjson比较合适,一款专为嵌入式设计的开源JSON解析库,设计简洁高效,用得挺多的。

下面简单介绍下cjson:

cJSON是由Dave Gamble开发的超轻量级C语言JSON解析库,代码仅一个.h和一个.c文件,完全开源,专为嵌入式环境优化。

 GitHub地址:

https://github.com/DaveGamble/cJSON

核心特点

• 极简内存占用:解析时仅需几KB内存,适合RAM稀缺的单片机(如STM32F103、ESP8266)。
• 纯C实现:无第三方依赖,跨平台支持(Keil、IAR、GCC均可编译),就两个文件,移植起来太简单了。
• 易用性:API简洁,10分钟即可上手。
• 灵活性:支持动态创建、修改、序列化和反序列化JSON数据。

下面是简单的cjson使用示例 :

1. 示例代码:创建JSON并输出字符串
#include"cJSON.h"

voidcreate_json(void){
    // 1. 创建根对象
    cJSON *root = cJSON_CreateObject();
    
    // 2. 添加键值对
    cJSON_AddStringToObject(root, "device", "ESP32");
    cJSON_AddNumberToObject(root, "temperature", 25.6);
    
    // 3. 添加嵌套对象
    cJSON *location = cJSON_CreateObject();
    cJSON_AddNumberToObject(location, "lat", 35.6895);
    cJSON_AddNumberToObject(location, "lon", 139.6917);
    cJSON_AddItemToObject(root, "location", location);

    // 4. 生成JSON字符串
    char *json_str = cJSON_Print(root);
    printf("JSON: %s\n", json_str);

    // 5. 释放内存(重要!)
    free(json_str);
    cJSON_Delete(root);
}

输出结果

{
  "device": "ESP32",
  "temperature": 25.6,
  "location": {
    "lat": 35.6895,
    "lon": 139.6917
  }
}
2. 解析JSON数据
voidparse_json(constchar *json_str){
    cJSON *root = cJSON_Parse(json_str);
    if (root == NULL) {
        printf("JSON解析失败!\n");
        return;
    }

    // 提取字段
    cJSON *device = cJSON_GetObjectItem(root, "device");
    cJSON *temp = cJSON_GetObjectItem(root, "temperature");
    cJSON *location = cJSON_GetObjectItem(root, "location");
    
    printf("设备名: %s, 温度: %.1f\n", device->valuestring, temp->valuedouble);
    printf("经纬度: (%.4f, %.4f)\n", 
           cJSON_GetObjectItem(location, "lat")->valuedouble,
           cJSON_GetObjectItem(location, "lon")->valuedouble);

    cJSON_Delete(root);
}


2

cjson在单片机中的主要应用

1、动态配置管理

痛点:传统配置方式需将参数硬编码在代码中,修改配置需重新编译固件,无法动态更新。
解决方案:将配置存储在JSON文件中,通过cJSON解析,增删都很方便,有较好的兼容性,支持OTA动态更新,当然还是会占一些内存。

示例代码:解析Wi-Fi配置

// config.json
{
  "wifi": {
    "ssid": "MyIoT",
    "password": "123456"
  },
  "sampling_interval": 5000
}
voidload_config(void){
    // 从Flash或SD卡读取JSON文件
    char *json_str = read_file("config.json");
    cJSON *root = cJSON_Parse(json_str);

    cJSON *wifi = cJSON_GetObjectItem(root, "wifi");
    char *ssid = cJSON_GetObjectItem(wifi, "ssid")->valuestring;
    char *password = cJSON_GetObjectItem(wifi, "password")->valuestring;
    int interval = cJSON_GetObjectItem(root, "sampling_interval")->valueint;

    printf("Wi-Fi: %s/%s, 采样间隔: %dms\n", ssid, password, interval);

    cJSON_Delete(root);
    free(json_str);
}
2、设备间通信协议

痛点:自定义二进制协议调试困难,扩展性差。
解决方案:使用JSON定义通信指令,可读性不错,方便调试扯皮,扩展性不错增删都能够较好兼容。

下面的例子是一个非常有意思的玩法,作为参数动态扩展。

示例代码:解析控制指令

// 收到指令:{"cmd": "set_led", "args": {"id": 1, "brightness": 80}}
voidhandle_command(const char *json_str){
    cJSON *root = cJSON_Parse(json_str);
    char *cmd = cJSON_GetObjectItem(root, "cmd")->valuestring;
    
    if (strcmp(cmd, "set_led") == 0) {
        cJSON *args = cJSON_GetObjectItem(root, "args");
        int id = cJSON_GetObjectItem(args, "id")->valueint;
        int brightness = cJSON_GetObjectItem(args, "brightness")->valueint;
        led_set(id, brightness);  // 实际控制函数
    }
    
    cJSON_Delete(root);
}
3、本地数据存储与日志记录

痛点:传感器数据若以二进制格式存储,导出后需专用工具解析。
解决方案:将数据序列化为JSON字符串,直接存储为文本文件。

示例代码:记录温度数据

voidlog_sensor_data(float temp, float humidity){
    cJSON *root = cJSON_CreateObject();
    cJSON_AddNumberToObject(root, "temp", temp);
    cJSON_AddNumberToObject(root, "humidity", humidity);
    cJSON_AddStringToObject(root, "timestamp", "2023-08-15T14:30:00Z");
    
    char *json_str = cJSON_PrintUnformatted(root);  // 紧凑模式节省空间
    write_to_sd_card("log.json", json_str);
    
    free(json_str);
    cJSON_Delete(root);
}

3

使用过程的注意事项

1、要注意调用cJSON_Delete()free()及时的释放资源,防止内存泄漏,不然跑着跑着就奔了~。

2、想省一些内存和带宽,json相关的命名最好不要太长。

3、cjson支持

cJSON_ParseWithOpts流式解析,这在解析大型JSON时比较有用。每接收到一部分数据即可立即解析,无需等待全部数据到达,算是一种低延时的增量处理。

而且对于单片机的RAM资源有限,一次性加载大型JSON字符串可能导致内存溢出。有了流式解析后,解析完部分数据后,可立即释放相关内存,维持低内存占用。

最后

      好了,今天就跟大家分享这么多了,如果你觉得有所收获,一定记得点个~

唯一、永久、免费分享嵌入式技术知识平台~

推荐专辑  点击蓝色字体即可跳转

☞  MCU进阶专辑图片

☞  嵌入式C语言进阶专辑图片

☞  “bug说”专辑图片

☞ 专辑|Linux应用程序编程大全

☞ 专辑|学点网络知识

☞ 专辑|手撕C语言

☞ 专辑|手撕C++语言

☞ 专辑|经验分享

☞ 专辑|电能控制技术

☞ 专辑 | 从单片机到Linux

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐