MQTT+JSON是物联网设备进行数据交互最常用的通讯协议,其中JSON协议非常适合用于明文传输、短包传输的场景。
认识理解和运用设计一个协议,一定要先了解其原理,再去认识其优缺点和适用范围。
目录
1、JSON概念表述
JSON数据结构是一个键值对的序列化对象或数组。
键,是一个字符串;
值,可以是对象,数组,数字,字符串其中的一种;
其中对象由花括号括起来并用逗号进行分割;
数组是由方括号括起来的,字符串是由双引号包围的,二者都由逗号分隔;
数字不需要括起来,直接用逗号分隔。
其本质就是一个字符串,可以跨语言、跨平台进行存储,并且字符串key-value的形式方便阅读。
cJSON库就是一种最典型的C语言JSON库代码。
非常适合在嵌入式设备上移植和使用。
2、cJSON库可能需要的修改
2.1 内存修改
该库默认使用的C标准库的内存函数。
如果你需要调试内存问题,或者需要统一内存使用方式,都可以更改如下位置的代码替换内存函数。
#if defined(_MSC_VER)
/* work around MSVC error C2322: '...' address of dllimport '...' is not static */
static void * CJSON_CDECL internal_malloc(size_t size)
{
return malloc(size);
}
static void CJSON_CDECL internal_free(void *pointer)
{
free(pointer);
}
static void * CJSON_CDECL internal_realloc(void *pointer, size_t size)
{
return realloc(pointer, size);
}
#else
#define internal_malloc malloc
#define internal_free free
#define internal_realloc realloc
#endif
2.2 打印number出现的科学计数法(参考)
static cJSON_bool print_number(const cJSON * const item, printbuffer * const output_buffer);
文件第511行,该函数执行sscanf之后,会出现导致打印number类型数值,结果出现科学计数法的问题(stm32F205芯片调试时出现的,当时内存资源极度紧张)。
修改方案如下(修改后解决):
具体原因我也没搞清楚,就先列一下解决方案。
#if 0
/* Try 15 decimal places of precision to avoid nonsignificant nonzero digits */
length = sprintf((char*)number_buffer, "%1.15g", d);
/* Check whether the original double can be recovered */
if ((sscanf((char*)number_buffer, "%lg", &test) != 1) || !compare_double((double)test, d))
{
/* If not, print with 17 decimal places of precision */
length = sprintf((char*)number_buffer, "%1.17g", d);
}
#else
length = sprintf((char*)number_buffer, "%d", (unsigned int)d);
#endif
3、JSON解析和处理的一般方法
3.1 定义
//头文件的必要包含关系和声明
#include "cJSON.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
typedef enum
{
ERROR_CODE_INTER = -1, //内部错误
ERROR_CODE_NONE = 0, //空/OK
ERROR_CODE_PARAM, //参数错误
} EnErrorCode;
typedef struct
{
char *CmdStr;
int (*CmdFun)(cJSON*);
} TyJsonCmdFun;
void JsonCmdAnalysis(uint8_t *pData, uint16_t unDataLen);
void JsonCmdResponse(cJSON *Json, int err, void *res);
void JsonCmdSend(uint8_t *pData, uint16_t unDataLen);
如上,定义了①函数返回的错误码,②json函数结构体,③函数解析和反馈的入口。
3.2 解析JSON
基于此,可以定义如下的函数表,
收到数据去解析时,判断json的固定键值对-如"cmd"字段,来执行对应的处理函数。
这里需要注意一个点:cJSON_Parse和cJSON_Delete。
cJSON_Parse函数用于将字节流转成JSON对象,其中存在内存申请动作,且无法通过free进行释放,只能通过cJSON_Delete进行释放。
int JsonCmdDeal_version(cJSON *Json);
int JsonCmdDeal_ota_start(cJSON *Json);
int JsonCmdDeal_ota_data(cJSON *Json);
int JsonCmdDeal_ota_end(cJSON *Json);
int JsonCmdDeal_driver_test(cJSON *Json);
const static TyJsonCmdFun JsonCmdFunTable[] =
{
{"version", JsonCmdDeal_version},
{"ota_start", JsonCmdDeal_ota_start},
{"ota_data", JsonCmdDeal_ota_data},
{"ota_end", JsonCmdDeal_ota_end},
{"driver_test", JsonCmdDeal_driver_test},
};
void JsonCmdAnalysis(uint8_t *pData, uint16_t unDataLen)
{
uint8_t i;
cJSON *JsonCmd = NULL;
JsonCmd = cJSON_Parse((char*)pData);
if(JsonCmd != NULL)
{
for(i=0;i<sizeof(JsonCmdFunTable)/sizeof(TyJsonCmdFun);i++)
{
if(strcmp(JsonCmdFunTable[i].CmdStr,cJSON_GetObjectItem(JsonCmd,"cmd")->valuestring) == 0)
{
JsonCmdFunTable[i].CmdFun(JsonCmd);
break;
}
}
cJSON_Delete(JsonCmd);
}
}
3.2 反馈/生成JSON
void JsonCmdResponse(cJSON *Json, int err, void *res)
{
char *json_buf = NULL;
int buf_len = 0;
cJSON* respond_json = NULL;
respond_json = cJSON_CreateObject();
if(respond_json != NULL)
{
cJSON_AddStringToObject(respond_json, "cmd", cJSON_GetObjectItem(Json,"cmd")->valuestring);
if(err == ERROR_CODE_NONE)
cJSON_AddStringToObject(respond_json, "rsq", "ok");
else
cJSON_AddNumberToObject(respond_json, "rsq", err);
json_buf = cJSON_PrintUnformatted(respond_json);
if(json_buf != NULL)
{
buf_len = strlen((char*)json_buf);
JsonCmdSend((uint8_t *)json_buf, buf_len);
free(json_buf);
}
cJSON_Delete(respond_json);
}
}
void JsonCmdSend(uint8_t *pData, uint16_t unDataLen)
{
//do nothing
}
int JsonCmdDeal_version(cJSON *Json)
{
int err = ERROR_CODE_NONE;
JsonCmdResponse(Json, err, NULL);
return err;
}
同样的,cJSON_CreateObject() 函数用于创建JSON对象,并随着后面用户调用cJSON_AddXXXXToObject类的函数产生内存申请(realloc),最终只能由cJSON_Delete释放内存。
额外的一点,就是cJSON的print函数,是可以直接由free函数进行释放的。
其中,cJSON_PrintUnformatted是将JSON对象打印成无格式化字符的字符串。
cJSON_Print则是带有格式化字符的(会带有很多的空格与换行)。
如果需要直观查看JSON数据,建议用cJSON_Print;
但是如果是应用业务执行,建议还是用cJSON_PrintUnformatted,尽可能减少字节量传输。
3.4 经常用到的JSON处理函数
//内存操作函数
CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value);//字符串转json
CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item);//打印json字符串1
CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item);//打印json字符串2
CJSON_PUBLIC(void) cJSON_Delete(cJSON *item);//释放json
//创建json对象
CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void);//创建json对象
CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void);//创建数组
//创建json对象-用于更新键值对
CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num);
CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string);
CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void);
//获取json对象
CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array);//获取数组大小
CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index);//获取数组的某一元素
CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string);//获取键值对
//添加json键值对
CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number);
CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string);
//判断键值对的值的类型
CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item);
//替换(更新)键值对
CJSON_PUBLIC(void) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem);
CJSON_PUBLIC(void) cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem);