如何在C++串口数据流的任意字符串中提取JSON对象?
正确处理串口流式JSON数据的方案
绝对不要用正则表达式处理JSON——JSON的嵌套结构(对象、数组嵌套)属于上下文无关语法,正则表达式只能处理正则语言,无法正确跟踪嵌套层级,必然会在复杂场景下失效(比如你提到的含嵌入式JSON的情况)。
最优实现思路:流式缓冲区+层级计数+JSON库验证
针对串口无帧头、数据分批到达的特点,正确的做法是通过缓冲区累积数据,结合括号/引号的层级跟踪识别潜在的完整JSON对象,再用成熟的JSON库验证解析,具体步骤如下:
1. 核心逻辑
- 维护一个持久化的输入缓冲区,串口收到的所有字节都追加到缓冲区,不丢弃任何数据。
- 遍历缓冲区时,跟踪三个状态:
braces_depth:大括号{}的嵌套层级(遇到{加1,}减1)brackets_depth:方括号[]的嵌套层级(遇到[加1,]减1)in_string:标记当前是否处于JSON字符串中(遇到未转义的"时切换状态,字符串内的括号不计数)
- 当遍历到某个位置时,若
braces_depth == 0、brackets_depth == 0且in_string == false,说明此处可能是一个完整JSON对象的结尾,提取该段子串用JSON库验证。 - 验证成功则处理该JSON对象,并从缓冲区移除已处理的部分;验证失败则继续遍历后续数据。
2. C++ 实现示例(基于nlohmann/json库)
nlohmann/json是轻量的单头文件JSON库,无需编译,直接包含即可使用:
#include <iostream> #include <string> #include <nlohmann/json.hpp> using json = nlohmann::json; // 串口数据缓冲区(可作为类成员或全局变量) std::string serial_buffer; void process_serial_input(const std::string& new_bytes) { serial_buffer += new_bytes; size_t pos = 0; int braces_depth = 0; int brackets_depth = 0; bool in_string = false; bool escaped = false; while (pos < serial_buffer.size()) { char c = serial_buffer[pos]; // 处理字符串内的字符(转义引号不触发状态切换) if (in_string) { if (escaped) { escaped = false; } else if (c == '\\') { escaped = true; } else if (c == '"') { in_string = false; } pos++; continue; } // 处理非字符串内的结构字符 switch (c) { case '{': braces_depth++; break; case '}': braces_depth--; // 检查是否回到根层级,尝试提取并验证JSON if (braces_depth == 0 && brackets_depth == 0) { std::string candidate = serial_buffer.substr(0, pos + 1); try { json parsed_json = json::parse(candidate); // 解析成功,处理JSON对象 std::cout << "解析到完整JSON:\n" << parsed_json.dump(4) << "\n" << std::endl; // 移除已处理的部分,重置状态重新遍历 serial_buffer = serial_buffer.substr(pos + 1); pos = 0; braces_depth = 0; brackets_depth = 0; in_string = false; escaped = false; continue; } catch (const json::parse_error&) { // 解析失败,说明不是完整对象,继续向后扫描 } } break; case '[': brackets_depth++; break; case ']': brackets_depth--; break; case '"': in_string = true; break; // 忽略空白字符,不影响JSON结构 case ' ': case '\t': case '\n': case '\r': break; default: break; } pos++; } } // 模拟串口分批发送数据 int main() { // 分批发送嵌套JSON的片段 process_serial_input("{\"a\": \"test\", \"b\": [1, "); process_serial_input("2, 3], \"c\": {\"d\": 4}}"); // 模拟连续发送多个JSON对象 process_serial_input("{\"x\": 5}{\"y\": 6"); process_serial_input("}"); return 0; }
3. 关键优势
- 适配串口流式数据:不管数据分多少次到达,只要缓冲区累积到完整JSON对象,就能正确识别。
- 支持嵌套JSON:通过层级计数完美处理对象、数组的嵌套场景。
- 容错性强:用JSON库验证确保不会误判不完整或语法错误的片段。
- 性能高效:仅遍历缓冲区一次,验证成功后移除已处理数据,无需重复扫描。
内容的提问来源于stack exchange,提问作者Kenn Sebesta
相关产品推荐
相关产品推荐

