/* * 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 #include #include #include #include #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