Files
ESP32_COMM/main/server/json_parse.h
T
小龙虾 f9cbcc396b feat(spi): 增强 SPI master:性能剖析与执行耗时跟踪
- 新增 `lastWorkerExecUs()`:获取最近一次 worker 任务执行耗时(µs)。
- 引入 `SpiProfile` 结构体:事务级详细剖析,记录 SPI 事务各阶段的时间戳。
- 实现 `lastProfile()`:诊断用,获取最近一次事务剖析快照。
- SPI 时钟配置提升至 13.3 MHz,改善性能。
- 修改前端 JavaScript 轮询逻辑:新增 uptime 检查,更好地处理 ESP32 重启状态。
- SPI 测速 HTML 的计时单位由毫秒改为微秒显示。
- 新建 `spi_ring_cache.h`:共享 SPI ring push 缓存工具。
- 新增 `http_util.h`:共享 HTTP 处理器工具,含 JSON 拼接与查询参数解析。
- 引入 `json_parse.h`:轻量级 JSON 解析器,针对扁平 JSON 对象设计。
- 新增 Windows 环境下的项目构建批处理脚本。
- 重构 CAN 反馈处理:从推送式改为请求驱动架构,降低内存占用、提升效率。
2026-08-02 17:37:19 +08:00

243 lines
8.1 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* json_parse.h - 极简 JSON 扁平对象解析器(cJSON 零堆分配替代)
*
* 项目中对 JSON 的使用全部是"解析前端发来的扁平 JSON 对象":
* { "sha256": "...", "verify_only": true, "total_size": 12345, "type": "esp32" }
* 无嵌套对象、无数组(can_send_batch 已自研流式解析)。
* 本解析器直接在调用方提供的可写字符串缓冲上做扫描,
* 不分配堆内存,字符串值通过就地 NUL 终止返回指针。
*
* 用法:
* JsonView root = jsonParse(buf); // buf 须为可写 char[]
* if (!root.valid) return error;
* JsonView sha = jsonGet(&root, "sha256");
* if (sha.type == JSON_STRING) { use(sha.str); }
* JsonView sz = jsonGet(&root, "total_size");
* if (sz.type == JSON_NUMBER) { uint32_t n = (uint32_t)sz.num; }
* // 无需 free —— 指针指向 buf,buf 由调用方管理
*/
#ifndef JSON_PARSE_H
#define JSON_PARSE_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef enum {
JSON_INVALID = 0,
JSON_NULL,
JSON_BOOL,
JSON_NUMBER,
JSON_STRING,
JSON_OBJECT,
} JsonType;
typedef struct {
JsonType type;
bool valid;
char *str; /* JSON_STRING:指向 buf 内字符串(不一定 NUL 终止!) */
size_t str_len; /* JSON_STRING:字符串长度(配合 str 使用,避免就地写 NUL) */
double num; /* JSON_NUMBER / JSON_BOOL */
char *obj_start; /* JSON_OBJECT:'{' 后的内容区 */
size_t obj_len;
} JsonView;
/**
* @brief 解析 JSON 字符串为根对象视图(就地扫描,可能写入 NUL 终止符)。
* @param json 可写 NUL 终止 JSON 字符串(须为顶层 { ... } 扁平对象)
* @return type=JSON_OBJECT 且 valid=true,或 valid=false 解析失败
*/
static inline JsonView jsonParse(char *json)
{
JsonView v = {};
if (!json) return v;
char *p = json;
while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') p++;
if (*p != '{') return v;
p++;
char *content = p;
int depth = 1;
while (*p) {
if (*p == '"') {
p++;
while (*p && *p != '"') {
if (*p == '\\' && p[1]) p++;
p++;
}
if (*p) p++;
} else if (*p == '{') {
depth++; p++;
} else if (*p == '}') {
depth--;
if (depth == 0) break;
p++;
} else {
p++;
}
}
if (depth != 0) return v;
v.type = JSON_OBJECT;
v.valid = true;
v.obj_start = content;
v.obj_len = (size_t)(p - content);
return v;
}
/**
* @brief 在扁平 JSON 对象中查找指定 key 的值。
* @return valid=true 表示找到;字符串值经就地 NUL 终止后通过 .str 返回
*/
static inline JsonView jsonGet(const JsonView *obj, const char *key)
{
JsonView result = {};
if (!obj || !obj->valid || obj->type != JSON_OBJECT || !key) return result;
char *p = obj->obj_start;
char *end = obj->obj_start + obj->obj_len;
while (p < end) {
while (p < end && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r' || *p == ',')) p++;
if (p >= end) break;
/* "key" */
if (*p != '"') break;
p++;
char *key_start = p;
while (p < end && *p != '"') p++;
if (p >= end) break;
size_t klen = (size_t)(p - key_start);
p++;
while (p < end && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')) p++;
if (p >= end || *p != ':') break;
p++;
while (p < end && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')) p++;
if (p >= end) break;
bool match = (strlen(key) == klen && strncmp(key_start, key, klen) == 0);
if (*p == '"') {
p++;
char *val_start = p;
while (p < end && *p != '"') {
if (*p == '\\' && p + 1 < end) p++;
p++;
}
if (p >= end) break;
if (match) {
/* 不就地写 NUL(会破坏后续 jsonGet 扫描);用 str_len 返回长度 */
result.type = JSON_STRING;
result.valid = true;
result.str = val_start;
result.str_len = (size_t)(p - val_start);
return result;
}
p++;
} else if (*p == 't' || *p == 'f') {
if (match) {
result.type = JSON_BOOL;
result.valid = true;
result.num = (*p == 't') ? 1.0 : 0.0;
return result;
}
while (p < end && *p != ',' && *p != '}' && *p != ' ' && *p != '\n' && *p != '\r') p++;
} else if (*p == 'n') {
if (match) {
result.type = JSON_NULL;
result.valid = true;
return result;
}
while (p < end && *p != ',' && *p != '}' && *p != ' ' && *p != '\n' && *p != '\r') p++;
} else {
char *val_start = p;
if (*p == '-') p++;
while (p < end && ((*p >= '0' && *p <= '9') || *p == '.' || *p == 'e' || *p == 'E' || *p == '+' || *p == '-')) p++;
if (match) {
/* 不就地写 NUL;strtod 可在非 NUL 终止串上工作(指定长度)。
* 但 strtod 需要终止符——用临时方式:记录起止,用 sscanf 或手动解析。
* 更简方案:buf 本身是 NUL 终止的(jsonParse 要求 NUL 终止输入),
* 而 *p 此处是数字后的分隔符(',' 或 '}' 或空白),不是 NUL。
* 我们临时保存 *p、写 NUL、解析、恢复 *p。 */
char saved = *p;
*p = '\0';
result.type = JSON_NUMBER;
result.valid = true;
result.num = strtod(val_start, nullptr);
*p = saved; /* 恢复,不影响后续 jsonGet 扫描 */
return result;
}
}
}
return result;
}
/* ── 便捷谓词(兼容 cJSON 调用风格)── */
static inline bool jsonIsString(const JsonView *item) {
return item && item->valid && item->type == JSON_STRING;
}
static inline bool jsonIsNumber(const JsonView *item) {
return item && item->valid && item->type == JSON_NUMBER;
}
static inline bool jsonIsBool(const JsonView *item) {
return item && item->valid && item->type == JSON_BOOL;
}
static inline bool jsonIsTrue(const JsonView *item) {
return item && item->valid && item->type == JSON_BOOL && item->num != 0.0;
}
/**
* @brief 获取字符串值指针和长度(字符串不一定 NUL 终止,须配合 str_len 使用)。
* @param item JSON 值视图
* @param out_len 可选,输出字符串长度
* @return 字符串指针(指向 buf 内部),或 nullptr
*/
static inline const char *jsonGetStringValue(const JsonView *item, size_t *out_len = nullptr) {
if (item && item->valid && item->type == JSON_STRING) {
if (out_len) *out_len = item->str_len;
return item->str;
}
if (out_len) *out_len = 0;
return nullptr;
}
/**
* @brief 比较字符串值是否等于指定 C 字符串。
* 因 jsonGet 不写 NUL 终止符,不能用 strcmp,须用此函数。
*/
static inline bool jsonStringEquals(const JsonView *item, const char *cstr) {
if (!jsonIsString(item) || !cstr) return false;
size_t clen = strlen(cstr);
return clen == item->str_len && strncmp(item->str, cstr, clen) == 0;
}
/**
* @brief 将字符串值拷贝到 NUL 终止的输出缓冲(安全截断)。
* @return 实际拷贝长度(不含 NUL)
*/
static inline size_t jsonGetStringCopy(const JsonView *item, char *out, size_t out_max) {
if (!jsonIsString(item) || !out || out_max == 0) return 0;
size_t n = (item->str_len < out_max - 1) ? item->str_len : out_max - 1;
memcpy(out, item->str, n);
out[n] = '\0';
return n;
}
static inline double jsonGetNumberValue(const JsonView *item) {
return (item && item->valid && item->type == JSON_NUMBER) ? item->num : 0.0;
}
#ifdef __cplusplus
}
#endif
#endif // JSON_PARSE_H