字符串处理与JSON解析:在资源受限设备上的轻量方案——流式解析、内存池
文章目录

每日一句正能量
所谓扛得住,很多时候只是我没倒下。
人们常把“扛得住”想象成游刃有余、面带微笑,但真实的坚持往往是狼狈的、沉默的,甚至只是“还没放弃”。把标准降到“没倒下”,反而给了自己更多喘息的空间——不必完美,只要还在场上。
前言
在物联网时代,JSON已成为嵌入式设备与云端通信的事实标准。无论是传感器数据上报、设备配置下发,还是OTA升级包描述,JSON格式无处不在。然而,当面对ROM仅有32KB、RAM仅有8KB的MCU时,传统的cJSON(ROM 25KB+)或Jansson(ROM 30KB+)显然过于"臃肿"。
更棘手的是,许多嵌入式场景要求边接收边解析——MQTT消息可能分多次到达,串口数据以字节流形式涌入,WiFi模块的缓冲区有限。此时,需要一次性加载完整JSON的DOM解析方案完全失效。
本文将系统讲解在资源受限设备上实现轻量字符串处理与流式JSON解析的完整技术方案,核心围绕流式解析(SAX模式)与内存池管理两大关键技术,并提供可直接部署到STM32、ESP8266、nRF52等平台的完整代码框架。
一、资源受限设备的JSON处理挑战
1.1 传统方案的困境

图1:资源受限设备上的字符串与JSON处理挑战。传统方案(cJSON/Jansson)ROM占用25KB+、RAM占用5KB+,且依赖动态内存分配;轻量方案通过流式解析、内存池、静态字符串和增量解析,将ROM压缩至3-8KB、RAM固定至1-2KB。
传统JSON库的核心问题:
| 问题 | 影响 | 根因 |
|---|---|---|
| ROM占用大 | 挤压应用代码空间 | 完整的DOM树构建、丰富的API |
| RAM动态分配 | 碎片化、不可预测 | malloc/free依赖堆实现 |
| 递归解析器 | 栈溢出风险 | 嵌套JSON的递归下降 |
| 需完整缓冲 | 无法处理流式数据 | DOM解析必须加载全部数据 |
典型资源对比:
| 方案 | ROM | RAM | malloc依赖 | 流式支持 |
|---|---|---|---|---|
| cJSON | ~25KB | 动态5KB+ | 是 | 否 |
| Jansson | ~32KB | 动态8KB+ | 是 | 否 |
| jsmn | ~3KB | ~1KB | 否 | 部分 |
| 自定义流式 | ~4KB | ~1.5KB | 否 | 是 |
1.2 嵌入式JSON处理的四大原则
- 零动态分配:不使用
malloc/free,全部使用静态内存或内存池 - O(1)内存占用:解析过程的内存占用与JSON大小无关
- 流式处理:支持逐字节输入,无需等待完整数据
- 可中断:解析过程可在任意点暂停和恢复
二、流式解析 vs DOM解析
2.1 两种解析范式的本质差异
=
图2:流式解析(SAX) vs DOM解析对比。DOM解析需要完整加载JSON并构建内存树后才能访问数据;流式解析逐Token处理,每个Token通过回调函数立即交付应用层,内存占用固定为O(1)。
DOM解析(Document Object Model):
- 将JSON完整解析为内存中的树形结构
- 解析完成后才能访问数据
- 内存占用与JSON大小成正比
- 适合:配置加载、小数据量、需要随机访问
流式解析(SAX - Simple API for XML/JSON):
- 逐Token扫描,边解析边回调
- 每个Token产生后立即交付
- 内存占用固定(仅状态机+当前Token)
- 适合:流式数据、大数据量、实时处理
2.2 流式解析的Token类型
一个标准的JSON流式解析器识别以下Token:
| Token类型 | 示例 | 回调时机 |
|---|---|---|
OBJECT_START |
{ |
遇到 { |
OBJECT_END |
} |
遇到 } |
ARRAY_START |
[ |
遇到 [ |
ARRAY_END |
] |
遇到 ] |
KEY |
"temperature" |
键名解析完成 |
STRING |
"sensor_01" |
字符串值解析完成 |
NUMBER |
23.5 |
数字解析完成 |
BOOLEAN |
true/false |
布尔值解析完成 |
NULL |
null |
null值解析完成 |
三、流式JSON解析器的状态机实现
3.1 状态机设计
流式JSON解析器的核心是有限状态机(FSM)。每个输入字符触发状态转换,无需回溯,内存占用固定。

图3:流式JSON解析器状态机。包含IDLE、OBJECT、KEY、COLON、VALUE、COMMA、ARRAY、STRING、NUMBER、ESCAPE、ERROR等状态。每个输入字符触发确定性状态转换,如遇到
{从IDLE转到OBJECT,遇到"进入STRING状态。
3.2 核心状态机代码
/**
* @file json_stream_parser.h
* @brief 流式JSON解析器头文件
* @version 1.0.0
*/
#ifndef JSON_STREAM_PARSER_H
#define JSON_STREAM_PARSER_H
#include <stdint.h>
#include <stdbool.h>
/* ========== 配置 ========== */
#define JSON_MAX_DEPTH 8 /* 最大嵌套深度 */
#define JSON_MAX_KEY_LEN 32 /* 最大键名长度 */
#define JSON_MAX_STRING_LEN 128 /* 最大字符串值长度 */
#define JSON_MAX_NUMBER_LEN 32 /* 最大数字长度 */
/* ========== Token类型 ========== */
typedef enum {
JSON_TOKEN_NONE = 0,
JSON_TOKEN_OBJECT_START, /* { */
JSON_TOKEN_OBJECT_END, /* } */
JSON_TOKEN_ARRAY_START, /* [ */
JSON_TOKEN_ARRAY_END, /* ] */
JSON_TOKEN_KEY, /* "key" */
JSON_TOKEN_STRING, /* "value" */
JSON_TOKEN_NUMBER, /* 123 / 45.67 */
JSON_TOKEN_BOOLEAN, /* true / false */
JSON_TOKEN_NULL, /* null */
} json_token_type_t;
/* ========== 解析状态 ========== */
typedef enum {
JSON_STATE_IDLE = 0,
JSON_STATE_OBJECT,
JSON_STATE_KEY,
JSON_STATE_COLON,
JSON_STATE_VALUE,
JSON_STATE_STRING,
JSON_STATE_NUMBER,
JSON_STATE_BOOLEAN,
JSON_STATE_NULL,
JSON_STATE_ESCAPE,
JSON_STATE_ARRAY,
JSON_STATE_COMMA,
JSON_STATE_ERROR,
} json_state_t;
/* ========== Token结构 ========== */
typedef struct {
json_token_type_t type;
char data[JSON_MAX_STRING_LEN]; /* 字符串/键名/数字的文本 */
uint16_t len;
bool is_key; /* 当前Token是否为键名 */
} json_token_t;
/* ========== 回调函数类型 ========== */
typedef void (*json_token_cb_t)(const json_token_t *token, void *user_ctx);
/* ========== 解析器结构 ========== */
typedef struct {
json_state_t state; /* 当前状态 */
json_state_t stack[JSON_MAX_DEPTH]; /* 状态栈(用于嵌套) */
uint8_t depth; /* 当前嵌套深度 */
char buffer[JSON_MAX_STRING_LEN]; /* 当前Token的累积缓冲区 */
uint16_t buf_pos; /* 缓冲区当前位置 */
json_token_cb_t token_cb; /* Token回调函数 */
void *user_ctx; /* 用户上下文 */
uint32_t line; /* 当前行号(用于错误定位) */
uint32_t col; /* 当前列号 */
} json_parser_t;
/* ========== API ========== */
void json_parser_init(json_parser_t *parser, json_token_cb_t cb, void *user_ctx);
int json_parser_feed(json_parser_t *parser, const char *data, uint16_t len);
int json_parser_feed_byte(json_parser_t *parser, char c);
void json_parser_reset(json_parser_t *parser);
bool json_parser_is_complete(const json_parser_t *parser);
#endif /* JSON_STREAM_PARSER_H */
/**
* @file json_stream_parser.c
* @brief 流式JSON解析器实现(状态机驱动)
*/
#include "json_stream_parser.h"
#include <string.h>
#include <ctype.h>
/* 状态压栈 */
static inline int state_push(json_parser_t *parser, json_state_t state) {
if (parser->depth >= JSON_MAX_DEPTH) {
return -1; /* 嵌套过深 */
}
parser->stack[parser->depth++] = state;
return 0;
}
/* 状态弹栈 */
static inline int state_pop(json_parser_t *parser) {
if (parser->depth == 0) {
return -1; /* 栈空 */
}
parser->depth--;
return 0;
}
/* 获取当前父状态 */
static inline json_state_t state_peek(const json_parser_t *parser) {
if (parser->depth == 0) return JSON_STATE_IDLE;
return parser->stack[parser->depth - 1];
}
/* 发送Token到回调 */
static void emit_token(json_parser_t *parser, json_token_type_t type) {
if (parser->token_cb == NULL) return;
json_token_t token;
token.type = type;
token.len = parser->buf_pos;
token.is_key = (type == JSON_TOKEN_KEY);
if (parser->buf_pos > 0) {
memcpy(token.data, parser->buffer, parser->buf_pos);
token.data[parser->buf_pos] = '\0';
} else {
token.data[0] = '\0';
}
parser->token_cb(&token, parser->user_ctx);
/* 重置缓冲区 */
parser->buf_pos = 0;
}
/* 缓冲区追加字符 */
static inline int buf_append(json_parser_t *parser, char c) {
if (parser->buf_pos >= JSON_MAX_STRING_LEN - 1) {
return -1; /* 缓冲区溢出 */
}
parser->buffer[parser->buf_pos++] = c;
return 0;
}
/**
* @brief 初始化解析器
*/
void json_parser_init(json_parser_t *parser, json_token_cb_t cb, void *user_ctx) {
memset(parser, 0, sizeof(json_parser_t));
parser->state = JSON_STATE_IDLE;
parser->token_cb = cb;
parser->user_ctx = user_ctx;
parser->line = 1;
parser->col = 1;
}
/**
* @brief 重置解析器
*/
void json_parser_reset(json_parser_t *parser) {
parser->state = JSON_STATE_IDLE;
parser->depth = 0;
parser->buf_pos = 0;
parser->line = 1;
parser->col = 1;
}
/**
* @brief 单字节Feed(核心状态机)
*/
int json_parser_feed_byte(json_parser_t *parser, char c) {
/* 跳过空白字符(部分状态除外) */
if (parser->state != JSON_STATE_STRING &&
parser->state != JSON_STATE_KEY &&
isspace((unsigned char)c)) {
if (c == '\n') { parser->line++; parser->col = 1; }
else { parser->col++; }
return 0;
}
switch (parser->state) {
case JSON_STATE_IDLE:
if (c == '{') {
emit_token(parser, JSON_TOKEN_OBJECT_START);
parser->state = JSON_STATE_OBJECT;
state_push(parser, JSON_STATE_OBJECT);
} else if (c == '[') {
emit_token(parser, JSON_TOKEN_ARRAY_START);
parser->state = JSON_STATE_ARRAY;
state_push(parser, JSON_STATE_ARRAY);
} else {
parser->state = JSON_STATE_ERROR;
return -1;
}
break;
case JSON_STATE_OBJECT:
if (c == '}') {
emit_token(parser, JSON_TOKEN_OBJECT_END);
state_pop(parser);
parser->state = (parser->depth > 0) ? state_peek(parser) : JSON_STATE_IDLE;
} else if (c == '"') {
parser->state = JSON_STATE_KEY;
parser->buf_pos = 0;
} else {
parser->state = JSON_STATE_ERROR;
return -1;
}
break;
case JSON_STATE_KEY:
if (c == '"') {
emit_token(parser, JSON_TOKEN_KEY);
parser->state = JSON_STATE_COLON;
} else if (c == '\\') {
parser->state = JSON_STATE_ESCAPE;
state_push(parser, JSON_STATE_KEY);
} else {
buf_append(parser, c);
}
break;
case JSON_STATE_COLON:
if (c == ':') {
parser->state = JSON_STATE_VALUE;
} else {
parser->state = JSON_STATE_ERROR;
return -1;
}
break;
case JSON_STATE_VALUE:
if (c == '{') {
emit_token(parser, JSON_TOKEN_OBJECT_START);
parser->state = JSON_STATE_OBJECT;
state_push(parser, JSON_STATE_OBJECT);
} else if (c == '[') {
emit_token(parser, JSON_TOKEN_ARRAY_START);
parser->state = JSON_STATE_ARRAY;
state_push(parser, JSON_STATE_ARRAY);
} else if (c == '"') {
parser->state = JSON_STATE_STRING;
parser->buf_pos = 0;
} else if (c == 't' || c == 'f') {
parser->state = JSON_STATE_BOOLEAN;
parser->buffer[0] = c;
parser->buf_pos = 1;
} else if (c == 'n') {
parser->state = JSON_STATE_NULL;
parser->buffer[0] = c;
parser->buf_pos = 1;
} else if (isdigit((unsigned char)c) || c == '-') {
parser->state = JSON_STATE_NUMBER;
parser->buffer[0] = c;
parser->buf_pos = 1;
} else {
parser->state = JSON_STATE_ERROR;
return -1;
}
break;
case JSON_STATE_STRING:
if (c == '"') {
emit_token(parser, JSON_TOKEN_STRING);
/* 判断接下来是逗号还是结束 */
parser->state = JSON_STATE_COMMA;
} else if (c == '\\') {
parser->state = JSON_STATE_ESCAPE;
state_push(parser, JSON_STATE_STRING);
} else {
buf_append(parser, c);
}
break;
case JSON_STATE_NUMBER:
if (isdigit((unsigned char)c) || c == '.' || c == 'e' || c == 'E' || c == '-' || c == '+') {
buf_append(parser, c);
} else {
/* 数字结束,回退一个字符 */
emit_token(parser, JSON_TOKEN_NUMBER);
parser->state = JSON_STATE_COMMA;
/* 重新处理当前字符 */
return json_parser_feed_byte(parser, c);
}
break;
case JSON_STATE_BOOLEAN:
buf_append(parser, c);
if (parser->buf_pos >= 4) {
if (strncmp(parser->buffer, "true", 4) == 0 ||
strncmp(parser->buffer, "false", 5) == 0) {
emit_token(parser, JSON_TOKEN_BOOLEAN);
parser->state = JSON_STATE_COMMA;
}
}
break;
case JSON_STATE_NULL:
buf_append(parser, c);
if (parser->buf_pos >= 4) {
if (strncmp(parser->buffer, "null", 4) == 0) {
emit_token(parser, JSON_TOKEN_NULL);
parser->state = JSON_STATE_COMMA;
}
}
break;
case JSON_STATE_ESCAPE: {
char decoded;
switch (c) {
case '"': decoded = '"'; break;
case '\\': decoded = '\\'; break;
case '/': decoded = '/'; break;
case 'b': decoded = '\b'; break;
case 'f': decoded = '\f'; break;
case 'n': decoded = '\n'; break;
case 'r': decoded = '\r'; break;
case 't': decoded = '\t'; break;
default: decoded = c; break; /* 简化处理 */
}
buf_append(parser, decoded);
json_state_t prev = state_peek(parser);
state_pop(parser);
parser->state = prev;
break;
}
case JSON_STATE_COMMA:
if (c == ',') {
parser->state = (state_peek(parser) == JSON_STATE_ARRAY) ?
JSON_STATE_VALUE : JSON_STATE_OBJECT;
} else if (c == '}') {
emit_token(parser, JSON_TOKEN_OBJECT_END);
state_pop(parser);
parser->state = (parser->depth > 0) ? state_peek(parser) : JSON_STATE_IDLE;
} else if (c == ']') {
emit_token(parser, JSON_TOKEN_ARRAY_END);
state_pop(parser);
parser->state = (parser->depth > 0) ? state_peek(parser) : JSON_STATE_IDLE;
} else {
/* 可能是下一个值的开始,回退处理 */
parser->state = (state_peek(parser) == JSON_STATE_ARRAY) ?
JSON_STATE_VALUE : JSON_STATE_OBJECT;
return json_parser_feed_byte(parser, c);
}
break;
case JSON_STATE_ARRAY:
if (c == ']') {
emit_token(parser, JSON_TOKEN_ARRAY_END);
state_pop(parser);
parser->state = (parser->depth > 0) ? state_peek(parser) : JSON_STATE_IDLE;
} else {
parser->state = JSON_STATE_VALUE;
return json_parser_feed_byte(parser, c);
}
break;
case JSON_STATE_ERROR:
return -1;
}
parser->col++;
return 0;
}
/**
* @brief 批量Feed数据
*/
int json_parser_feed(json_parser_t *parser, const char *data, uint16_t len) {
for (uint16_t i = 0; i < len; i++) {
int ret = json_parser_feed_byte(parser, data[i]);
if (ret != 0) return ret;
}
return 0;
}
/**
* @brief 检查解析是否完成
*/
bool json_parser_is_complete(const json_parser_t *parser) {
return (parser->state == JSON_STATE_IDLE || parser->state == JSON_STATE_COMMA)
&& parser->depth == 0;
}
四、内存池管理:告别malloc
4.1 为什么嵌入式场景需要内存池?

图4:内存池管理原理。(a) 传统malloc的碎片化问题:多次分配释放后,碎片总空间虽大但无连续块,导致申请失败;(b) 内存池管理:预分配固定大小的块,通过空闲链表管理,分配时间确定O(1),零碎片,不依赖堆。
传统malloc的致命缺陷:
- 时间不确定:堆遍历查找合适块,最坏情况 O ( n ) O(n) O(n)
- 内存碎片:频繁分配释放导致碎片化
- 无边界检查:越界写入可能破坏堆元数据
- 依赖实现:某些RTOS或裸机环境无堆实现
内存池的核心优势:
- 预分配:系统启动时一次性分配,运行时不再向系统申请
- 固定块大小:消除碎片,分配时间 O ( 1 ) O(1) O(1)
- 边界保护:可添加Magic Number检测越界
- 零堆依赖:可使用静态数组实现
4.2 内存池实现
/**
* @file memory_pool.h
* @brief 固定块大小内存池
*/
#ifndef MEMORY_POOL_H
#define MEMORY_POOL_H
#include <stdint.h>
#include <stdbool.h>
#define POOL_BLOCK_SIZE 64 /* 每块大小(字节) */
#define POOL_BLOCK_COUNT 16 /* 总块数 */
#define POOL_MAGIC 0xDEAD /* 边界检测魔数 */
typedef struct pool_block {
struct pool_block *next; /* 空闲链表指针 */
uint16_t magic; /* 边界检测 */
uint8_t data[POOL_BLOCK_SIZE]; /* 用户数据区 */
} pool_block_t;
typedef struct {
pool_block_t blocks[POOL_BLOCK_COUNT]; /* 预分配的块数组 */
pool_block_t *free_list; /* 空闲链表头 */
uint16_t used_count; /* 已用块数 */
uint16_t max_used; /* 历史最大使用量 */
} memory_pool_t;
void pool_init(memory_pool_t *pool);
void *pool_alloc(memory_pool_t *pool);
void pool_free(memory_pool_t *pool, void *ptr);
uint16_t pool_used(const memory_pool_t *pool);
bool pool_check_overflow(const memory_pool_t *pool);
#endif /* MEMORY_POOL_H */
/**
* @file memory_pool.c
* @brief 内存池实现
*/
#include "memory_pool.h"
#include <string.h>
/**
* @brief 初始化内存池
*/
void pool_init(memory_pool_t *pool) {
memset(pool, 0, sizeof(memory_pool_t));
/* 构建空闲链表 */
pool->free_list = &pool->blocks[0];
for (uint16_t i = 0; i < POOL_BLOCK_COUNT - 1; i++) {
pool->blocks[i].next = &pool->blocks[i + 1];
pool->blocks[i].magic = POOL_MAGIC;
}
pool->blocks[POOL_BLOCK_COUNT - 1].next = NULL;
pool->blocks[POOL_BLOCK_COUNT - 1].magic = POOL_MAGIC;
}
/**
* @brief 分配一块内存(O(1))
*/
void *pool_alloc(memory_pool_t *pool) {
if (pool->free_list == NULL) {
return NULL; /* 内存池耗尽 */
}
pool_block_t *block = pool->free_list;
pool->free_list = block->next;
block->next = NULL;
pool->used_count++;
if (pool->used_count > pool->max_used) {
pool->max_used = pool->used_count;
}
return block->data;
}
/**
* @brief 释放内存(O(1))
*/
void pool_free(memory_pool_t *pool, void *ptr) {
if (ptr == NULL) return;
/* 从data指针反推block结构 */
pool_block_t *block = (pool_block_t *)((uint8_t *)ptr -
offsetof(pool_block_t, data));
/* 边界检测 */
if (block->magic != POOL_MAGIC) {
/* 内存越界或重复释放! */
return;
}
block->next = pool->free_list;
pool->free_list = block;
pool->used_count--;
}
/**
* @brief 获取已用块数
*/
uint16_t pool_used(const memory_pool_t *pool) {
return pool->used_count;
}
4.3 多级内存池(应对不同大小需求)
单一固定块大小无法应对不同需求,可实现多级内存池:
#define POOL_LEVELS 3
typedef struct {
memory_pool_t small; /* 16字节块,32个 */
memory_pool_t medium; /* 64字节块,16个 */
memory_pool_t large; /* 256字节块,4个 */
} multi_pool_t;
void *mpool_alloc(multi_pool_t *mp, uint16_t size) {
if (size <= 16) return pool_alloc(&mp->small);
if (size <= 64) return pool_alloc(&mp->medium);
if (size <= 256) return pool_alloc(&mp->large);
return NULL; /* 超出范围 */
}
五、轻量字符串处理
5.1 安全的字符串操作
标准C字符串函数(strcpy、strcat、sprintf)无边界检查,是嵌入式系统的安全隐患。必须实现安全的替代版本:
/**
* @brief 安全的字符串拷贝
*/
static inline int safe_strcpy(char *dst, const char *src, uint16_t dst_size) {
if (dst_size == 0) return -1;
uint16_t i;
for (i = 0; i < dst_size - 1 && src[i] != '\0'; i++) {
dst[i] = src[i];
}
dst[i] = '\0';
return (src[i] == '\0') ? 0 : -1; /* 0=成功, -1=截断 */
}
/**
* @brief 安全的字符串拼接
*/
static inline int safe_strcat(char *dst, const char *src, uint16_t dst_size) {
uint16_t dst_len = 0;
while (dst_len < dst_size && dst[dst_len] != '\0') dst_len++;
if (dst_len >= dst_size) return -1;
return safe_strcpy(dst + dst_len, src, dst_size - dst_len);
}
/**
* @brief 安全的整数转字符串(无sprintf依赖)
*/
static inline int safe_itoa(int32_t value, char *buf, uint16_t buf_size) {
if (buf_size < 2) return -1;
uint16_t pos = 0;
bool negative = (value < 0);
if (negative) {
if (pos >= buf_size - 1) return -1;
buf[pos++] = '-';
value = -value;
}
/* 逆序生成数字 */
char temp[12];
uint16_t temp_pos = 0;
do {
temp[temp_pos++] = '0' + (value % 10);
value /= 10;
} while (value > 0 && temp_pos < 11);
/* 反转到输出缓冲区 */
for (int i = temp_pos - 1; i >= 0; i--) {
if (pos >= buf_size - 1) return -1;
buf[pos++] = temp[i];
}
buf[pos] = '\0';
return 0;
}
/**
* @brief 安全的浮点转字符串(简化版)
*/
static inline int safe_ftoa(float value, char *buf, uint16_t buf_size, uint8_t decimals) {
if (buf_size < 4) return -1;
bool negative = (value < 0);
if (negative) value = -value;
int32_t int_part = (int32_t)value;
float frac_part = value - int_part;
uint16_t pos = 0;
if (negative) buf[pos++] = '-';
/* 整数部分 */
char int_buf[12];
safe_itoa(int_part, int_buf, sizeof(int_buf));
uint16_t int_len = 0;
while (int_buf[int_len]) int_len++;
if (pos + int_len >= buf_size) return -1;
memcpy(buf + pos, int_buf, int_len);
pos += int_len;
/* 小数点 */
if (pos >= buf_size - 1) return -1;
buf[pos++] = '.';
/* 小数部分 */
for (uint8_t i = 0; i < decimals && pos < buf_size - 1; i++) {
frac_part *= 10;
int digit = (int)frac_part;
buf[pos++] = '0' + digit;
frac_part -= digit;
}
buf[pos] = '\0';
return 0;
}
5.2 字符串常量池
对于频繁使用的字符串(如JSON键名),使用字符串常量池避免重复存储:
/* 使用枚举+查找表替代字符串比较 */
typedef enum {
STR_TEMPERATURE = 0,
STR_HUMIDITY,
STR_PRESSURE,
STR_STATUS,
STR_COUNT
} string_id_t;
static const char *const string_pool[] = {
[STR_TEMPERATURE] = "temperature",
[STR_HUMIDITY] = "humidity",
[STR_PRESSURE] = "pressure",
[STR_STATUS] = "status",
};
/* O(1)字符串ID查找(使用完美哈希或二分查找) */
string_id_t string_to_id(const char *str) {
/* 简化示例:线性查找,实际可用哈希 */
for (uint8_t i = 0; i < STR_COUNT; i++) {
if (strcmp(str, string_pool[i]) == 0) return i;
}
return STR_COUNT; /* 未找到 */
}
六、增量解析与串口数据流处理
6.1 流式数据接收架构

图6:增量解析——串口/WiFi流式数据处理架构。数据流(如
{"t":23.5,"h":65})逐字节进入环形缓冲区,流式解析器从缓冲区读取并实时解析,通过回调函数交付结构化数据。无需完整缓冲,内存O(1),中断安全。
6.2 环形缓冲区实现
/**
* @file ring_buffer.h
* @brief 线程安全的环形缓冲区
*/
#ifndef RING_BUFFER_H
#define RING_BUFFER_H
#include <stdint.h>
#include <stdbool.h>
#define RING_BUF_SIZE 256 /* 必须是2的幂,便于位运算优化 */
typedef struct {
volatile uint8_t buffer[RING_BUF_SIZE];
volatile uint16_t head; /* 写指针 */
volatile uint16_t tail; /* 读指针 */
} ring_buffer_t;
static inline void ring_init(ring_buffer_t *rb) {
rb->head = 0;
rb->tail = 0;
}
static inline bool ring_is_empty(const ring_buffer_t *rb) {
return rb->head == rb->tail;
}
static inline uint16_t ring_count(const ring_buffer_t *rb) {
return (uint16_t)(rb->head - rb->tail);
}
static inline bool ring_is_full(const ring_buffer_t *rb) {
return ring_count(rb) >= RING_BUF_SIZE;
}
/* 中断中调用(写入) */
static inline bool ring_put(ring_buffer_t *rb, uint8_t data) {
uint16_t next = (rb->head + 1) & (RING_BUF_SIZE - 1);
if (next == rb->tail) return false; /* 满 */
rb->buffer[rb->head] = data;
rb->head = next;
return true;
}
/* 主循环中调用(读取) */
static inline bool ring_get(ring_buffer_t *rb, uint8_t *data) {
if (ring_is_empty(rb)) return false;
*data = rb->buffer[rb->tail];
rb->tail = (rb->tail + 1) & (RING_BUF_SIZE - 1);
return true;
}
#endif /* RING_BUFFER_H */
6.3 完整的串口JSON解析示例
#include "json_stream_parser.h"
#include "ring_buffer.h"
#include "memory_pool.h"
/* 全局实例 */
static json_parser_t g_parser;
static ring_buffer_t g_uart_rx;
static memory_pool_t g_json_pool;
/* 解析结果存储 */
typedef struct {
float temperature;
float humidity;
bool valid;
} sensor_data_t;
static sensor_data_t g_sensor_data;
/* Token回调:提取感兴趣的键值 */
static void on_json_token(const json_token_t *token, void *ctx) {
static char current_key[JSON_MAX_KEY_LEN] = {0};
(void)ctx;
switch (token->type) {
case JSON_TOKEN_KEY:
safe_strcpy(current_key, token->data, sizeof(current_key));
break;
case JSON_TOKEN_NUMBER:
if (strcmp(current_key, "temperature") == 0) {
g_sensor_data.temperature = (float)atof(token->data);
} else if (strcmp(current_key, "humidity") == 0) {
g_sensor_data.humidity = (float)atof(token->data);
}
break;
case JSON_TOKEN_OBJECT_END:
g_sensor_data.valid = true;
break;
default:
break;
}
}
/* UART中断服务程序 */
void UART_IRQHandler(void) {
while (UART_DataAvailable()) {
uint8_t ch = UART_ReadByte();
ring_put(&g_uart_rx, ch);
}
}
/* 主循环中的解析任务 */
void JSON_ParseTask(void) {
uint8_t ch;
while (ring_get(&g_uart_rx, &ch)) {
if (json_parser_feed_byte(&g_parser, (char)ch) != 0) {
/* 解析错误,重置 */
json_parser_reset(&g_parser);
continue;
}
if (json_parser_is_complete(&g_parser)) {
/* JSON解析完成,处理数据 */
if (g_sensor_data.valid) {
ProcessSensorData(&g_sensor_data);
g_sensor_data.valid = false;
}
json_parser_reset(&g_parser);
}
}
}
/* 系统初始化 */
void System_Init(void) {
ring_init(&g_uart_rx);
pool_init(&g_json_pool);
json_parser_init(&g_parser, on_json_token, NULL);
UART_Init();
UART_EnableIRQ();
}
七、不同JSON库的选型对比

图7:嵌入式JSON库资源与性能对比。(a) 资源占用:cJSON ROM 25KB/RAM 8KB,Jansson ROM 32KB/RAM 12KB,jsmn仅ROM 3KB/RAM 1KB,自定义流式解析ROM 4KB/RAM 1.5KB;(b) 解析性能:流式解析在4KB数据下仅需约200μs,比cJSON快约10倍。
| 库 | ROM | RAM | 流式 | 回调 | 适用场景 |
|---|---|---|---|---|---|
| cJSON | ~25KB | 动态5KB+ | 否 | 否 | 配置加载、小数据 |
| Jansson | ~32KB | 动态8KB+ | 否 | 否 | 复杂JSON、需修改 |
| jsmn | ~3KB | ~1KB | 部分 | 否 | 极简Token提取 |
| tiny-json | ~5KB | ~2KB | 否 | 否 | 小型DOM解析 |
| 自定义流式 | ~4KB | ~1.5KB | 是 | 是 | 流式数据、实时处理 |
选型建议:
- jsmn:仅需提取Token、不修改JSON的场景
- 自定义流式:串口/MQTT流式数据、实时性要求高的场景
- cJSON:配置加载、数据量小、需要修改JSON的场景
八、完整轻量JSON解析系统架构

图8:轻量JSON解析系统完整架构。四层架构:输入层(UART/WiFi/BLE/LoRa/CAN)→ 环形缓冲区+流式接收 → 解析核心层(状态机、Token提取、内存池、回调系统)→ 应用层(传感器数据、配置参数、OTA升级、日志上报、远程控制)。系统总开销:ROM ~4KB,RAM ~1.5KB,零malloc依赖。
九、调试与验证
9.1 内存使用监控
/* 内存池使用监控 */
void Pool_Monitor(void) {
printf(\"Pool used: %d/%d (max: %d)\\n\",
pool_used(&g_json_pool), POOL_BLOCK_COUNT, g_json_pool.max_used);
}
/* 解析器状态监控 */
void Parser_Monitor(const json_parser_t *parser) {
printf(\"State: %d, Depth: %d, Buf: %d\\n\",
parser->state, parser->depth, parser->buf_pos);
}
9.2 测试用例
/* 测试JSON */
const char *test_json = \"{\\\"device\\\":\\\"sensor01\\\",\\\"data\\\":{\\\"t\\\":25.5,\\\"h\\\":60}}\";
void Test_Parser(void) {
json_parser_t parser;
json_parser_init(&parser, on_json_token, NULL);
/* 逐字节Feed */
for (uint16_t i = 0; test_json[i] != '\0'; i++) {
int ret = json_parser_feed_byte(&parser, test_json[i]);
if (ret != 0) {
printf(\"Parse error at char %d\\n\", i);
return;
}
}
if (json_parser_is_complete(&parser)) {
printf(\"Parse OK! Temp=%.1f, Hum=%.1f\\n\",
g_sensor_data.temperature, g_sensor_data.humidity);
}
}
十、总结
在资源受限的嵌入式设备上处理JSON,核心挑战不是"能不能解析",而是"如何在有限的ROM/RAM下高效、安全、实时地解析"。
核心要点回顾:
- 流式解析:状态机驱动,逐Token处理,内存O(1),适合流式数据
- 内存池:预分配固定块,零碎片,O(1)分配,不依赖堆
- 安全字符串:边界检查的strcpy/strcat替代,避免缓冲区溢出
- 环形缓冲:中断安全的字节流接收,解耦数据到达与解析时序
- 零malloc:全部使用静态内存或内存池,消除动态分配的不确定性
掌握本文所述技术,你将能够在任何资源受限的MCU上实现轻量、高效、可靠的JSON解析能力。
转载自:https://blog.csdn.net/u014727709/article/details/162611617
欢迎 👍点赞✍评论⭐收藏,欢迎指正
更多推荐
所有评论(0)