4. JSON指令格式:数据结构、封装规范与字段命名
在物联网设备远程控制场景中,JSON(JavaScript Object Notation)凭借其轻量、自描述、跨语言的特点,已成为指令封装的事实标准。本章将系统讲解JSON数据结构、指令封装规范、字段命名规则以及嵌套对象与数组的使用方法,确保你能够设计出既符合规范又易于调试的指令格式。
4.1 JSON基础数据结构
JSON支持六种基本数据类型,在指令封装中各有典型用途:
| 数据类型 | 示例 | 指令场景 |
|---|---|---|
| 字符串(String) | "device_id": "sensor_01" |
设备标识、指令名称、状态描述 |
| 数字(Number) | "temperature": 25.6 |
传感器数值、定时时长、阈值 |
| 布尔(Boolean) | "power": true |
开关状态、使能标志 |
| 空值(Null) | "error": null |
无错误、未初始化字段 |
| 对象(Object) | "config": { "mode": "auto" } |
参数分组、嵌套配置 |
| 数组(Array) | "targets": [1, 2, 3] |
批量操作、多点控制 |
实践要点:在嵌入式设备端解析JSON时,优先使用数字类型而非字符串表示数值,避免在MCU上进行额外的类型转换,减少内存开销。
4.2 指令封装规范
一个标准的RemoteCommand指令JSON应包含以下核心字段,形成统一的指令信封:
{
"cmd_id": "CMD_20250321_001",
"type": "control",
"timestamp": 1711000000,
"source": "cloud_server",
"target": {
"device_id": "sensor_01",
"group": "temperature_sensors"
},
"action": "set_config",
"params": {
"sampling_interval": 10,
"threshold": 30.0,
"alarm_enabled": true
},
"ttl": 60,
"priority": 2
}
字段说明:
- cmd_id:全局唯一指令ID,用于去重和追踪
- type:指令类型(control/query/config/ota)
- timestamp:Unix时间戳,用于时序校验
- source:指令发起方标识
- target:目标设备对象,支持单设备或设备组
- action:具体操作名称
- params:操作参数对象
- ttl:指令生存时间(秒),超时丢弃
- priority:优先级(0-5,数值越大优先级越高)
避坑指南:不要将指令参数直接平铺在顶层字段中。始终使用
params对象封装操作参数,这样便于扩展和版本兼容。例如,新增参数时只需在params中添加字段,不会破坏顶层结构。
4.3 字段命名规则
统一的命名规则能显著提升指令的可读性和跨团队协作效率。推荐采用以下规范:
| 规则项 | 推荐做法 | 反例 |
|---|---|---|
| 命名风格 | snake_case(小写+下划线) | camelCase / PascalCase / kebab-case |
| 字段长度 | 2-4个单词,不超过30字符 | very_long_and_descriptive_field_name |
| 语义明确 | device_id / sampling_interval | d_id / si / val1 |
| 布尔字段 | is_enabled / has_error / use_ssl | enabled / error / ssl |
| 时间字段 | timestamp / expire_at / created_at | time / t / date |
| 枚举值 | UPPER_SNAKE_CASE | lowercase / mixedCase |
团队约定:建议在项目初期制定JSON字段命名规范文档,并通过lint工具(如ajv、jsonschema)进行自动化校验,避免因命名不一致导致的解析错误。
4.4 嵌套对象与数组
实际物联网场景中,指令往往需要表达复杂的层级关系。嵌套对象和数组是实现这一目标的核心手段。
4.4.1 嵌套对象
嵌套对象用于将相关参数组织在一起,形成清晰的层级结构:
{
"cmd_id": "CMD_20250321_002",
"action": "set_control_params",
"params": {
"pid": {
"kp": 1.5,
"ki": 0.02,
"kd": 0.1,
"output_limit": {
"min": 0,
"max": 100
}
},
"sensor_config": {
"type": "temperature",
"filter": {
"method": "moving_average",
"window_size": 5
}
}
}
}
嵌套深度建议不超过3层,过深的嵌套会增加解析复杂度和内存占用,尤其在资源受限的嵌入式设备上。
4.4.2 数组的使用
数组用于表达批量操作、多点控制或时序数据:
{
"cmd_id": "CMD_20250321_003",
"action": "batch_set",
"params": {
"devices": [
{
"id": "sensor_01",
"target_temp": 25.0
},
{
"id": "sensor_02",
"target_temp": 26.5
},
{
"id": "sensor_03",
"target_temp": 24.0
}
],
"schedule": ["08:00", "12:00", "18:00"],
"priority_list": [2, 1, 3]
}
}
性能提示:在嵌入式设备端解析数组时,建议预先分配最大容量,避免动态内存分配带来的碎片和不确定性。例如,使用静态数组+长度计数器的模式。
4.5 JSON指令结构总览
下图展示了RemoteCommand指令的完整JSON结构,包含指令信封、目标设备、操作参数以及嵌套对象和数组的典型用法:
4.6 指令校验与调试
在设备端解析JSON指令前,建议进行以下校验:
- 结构校验:检查必填字段是否存在,类型是否正确
- 范围校验:数值字段是否在合理范围内(如温度-40~85℃)
- 嵌套深度校验:防止恶意构造的深层嵌套导致栈溢出
- 数组长度校验:确保不超过设备端预分配缓冲区大小
// 嵌入式C语言校验示例(伪代码)
typedef struct {
char cmd_id[32];
int type;
long timestamp;
char source[16];
int priority;
// 嵌套对象
Target target;
Params params;
int ttl;
} RemoteCommand;
int validate_json_command(const char* json_str) {
// 1. 解析JSON
cJSON* root = cJSON_Parse(json_str);
if (root == NULL) return -1;
// 2. 校验必填字段
if (!cJSON_HasObjectItem(root, "cmd_id")) return -2;
if (!cJSON_HasObjectItem(root, "action")) return -3;
// 3. 校验嵌套深度(递归检查)
if (get_nested_depth(root) > MAX_NEST_DEPTH) return -4;
// 4. 校验数组长度
cJSON* devices = cJSON_GetObjectItem(root, "devices");
if (devices && cJSON_IsArray(devices)) {
int len = cJSON_GetArraySize(devices);
if (len > MAX_DEVICE_COUNT) return -5;
}
cJSON_Delete(root);
return 0; // 校验通过
}
调试技巧:在开发阶段,建议在设备端串口输出JSON解析的详细日志,包括每个字段的解析结果和耗时。使用
cJSON_Print()可以格式化输出解析后的JSON树,便于对比原始指令。
4.7 常见错误与规避
| 错误类型 | 错误示例 | 正确做法 |
|---|---|---|
| 字段名拼写不一致 | deviceId vs device_id |
统一使用snake_case |
| 类型不匹配 | "priority": "2"(字符串) |
"priority": 2(数字) |
| 嵌套过深 | 对象嵌套超过5层 | 控制在3层以内 |
| 数组元素类型不一致 | [1, "two", true] |
保持元素类型一致 |
| 缺少默认值 | 可选字段未提供时解析失败 | 设备端设定合理的默认值 |
通过遵循上述JSON指令格式规范,你可以构建出结构清晰、易于扩展、便于调试的远程控制指令体系,为后续的设备端响应处理奠定坚实基础。