最近在修已經離職同僚的代碼,它的代碼主要是負責與雲平台進行互動的,互動的協定采用的是标準的JSON格式;他用的是cJSON庫來進行解析。不幸的是,大佬的防禦性程式設計技能極差,很多地方該進行記憶體釋放的卻沒有進行記憶體釋放,還有很多地方該進行指針判空也沒有進行指針判空,而是直接就拿來用了。在多線程環境裡,這樣的做法極其危險。是以,在我和另外一位同僚接手,出現了大量的白屏、卡死等問題。後來,經過一段時間的定位,發現是程式編寫過程中不嚴謹造成的。
除了以上問題以外,由于我們的嵌入式平台采用的是低成本的ARM晶片,RAM隻有32MB,是以使用cJSON庫在解析的過程中可能較占用資源;在後期程式重構時可能會考慮使用jsmn來進行雲平台資料互動的解析。
本期給大家帶來的開源項目是 jsmn,一個資源占用極小的json解析器,号稱世界上最快,作者zserge,目前收獲 2.9K 個 star,遵循 MIT 開源許可協定。
jsmn主要有以下特性:
- 沒有任何庫依賴關系;
- 文法與C89相容,代碼可移植性高;
- 沒有任何動态記憶體配置設定
- 極小的代碼占用
- API隻有兩個,極其簡潔
項目位址:https://github.com/zserge/jsmn
移植jsmn
移植思路
開源項目在移植過程中主要參考項目的readme文檔,一般隻需兩步:
- ① 添加源碼到裸機工程中;
- ② 實作需要的接口;
準備裸機工程
本文中我使用的是小熊派IoT開發套件,主要晶片為STM32L431RCT6:
移植之前需要準備一份裸機工程,我使用STM32CubeMX生成,需要初始化以下配置:
- 配置一個序列槽用于發送資料;
- printf重定向
具體過程可以參考:
- STM32CubeMX_07 | 使用USART發送和接收資料(中斷模式)
- STM32CubeMX_09 | 重定向printf函數到序列槽輸出的多種方法
添加jsmn到工程中
① 複制jsmn源碼到工程中:
② 将 jsmn.h 檔案添加到keil中(沒有實質作用,友善編輯):
③ 添加jsmn頭檔案路徑:
嵌入式物聯網需要學的東西真的非常多,千萬不要學錯了路線和内容,導緻工資要不上去!
無償分享大家一個資料包,差不多150多G。裡面學習内容、面經、項目都比較新也比較全!某魚上買估計至少要好幾十。
點選這裡找小助理0元領取:點選文中藍色字型即可領取
使用jsmn解析json資料
準備工作
① 包含jsmn頭檔案
使用時包含頭檔案,因為jsmn的函數定義也是在頭檔案中,是以第一次添加的時候,可以直接添加:
/* USER CODE BEGIN Includes */
#include "jsmn.h"
#include <stdio.h> //用于printf列印
#include <string.h> //用于字元串處理
/* USER CODE END Includes */
已經使用過之後,在别的檔案中繼續使用時,需要這樣添加,且順序不可互換:
/* USER CODE BEGIN 0 */
#define JSMN_HEADER
#include "jsmn.h"
/* USER CODE END 0 */
否則會造成函數重定義:
② 設定一段原始json資料
在main.c中設定原始的json資料,用于後續解析:
/* USER CODE BEGIN PV */
static const char *JSON_STRING =
"{\"user\": \"johndoe\", \"admin\": false, \"uid\": 1000,\n"
"\"groups\": [\"users\", \"wheel\", \"audio\", \"video\"]}";
/* USER CODE END PV */
③ 開辟一塊存放token的數組(token池)
jsmn中,每個資料段解析出來之後是一個token,關于token的詳細解釋,請參考下文第4.1小節。
/* USER CODE BEGIN PV */
jsmntok_t t[128];
/* USER CODE END PV */
④ 編寫在原始JSON資料中的字元串比較函數:
static int jsoneq(const char *json, jsmntok_t *tok, const char *s) {
if (tok->type == JSMN_STRING && (int)strlen(s) == tok->end - tok->start &&
strncmp(json + tok->start, s, tok->end - tok->start) == 0) {
return 0;
}
return -1;
}
建立并初始化解析器
在main函數的開始建立解析器:
/* USER CODE BEGIN 1 */
int r;
int i;
jsmn_parser p;//jsmn解析器
/* USER CODE END 1 */
在随後外設初始化完成之後的代碼中初始化解析器:
/* USER CODE BEGIN 2 */
jsmn_init(&p);
/* USER CODE END 2 */
解析資料,擷取token
r = jsmn_parse(&p, JSON_STRING, strlen(JSON_STRING), t,sizeof(t) / sizeof(t[0]));
if (r < 0) {
printf("Failed to parse JSON: %d\n", r);
return 1;
}
/* Assume the top-level element is an object */
if (r < 1 || t[0].type != JSMN_OBJECT) {
printf("Object expected\n");
return 1;
}
3.4. 逐個解析token
/* Loop over all keys of the root object */
for (i = 1; i < r; i++)
{
if (jsoneq(JSON_STRING, &t[i], "user") == 0)
{
/* We may use strndup() to fetch string value */
printf("- user: %.*s\n", t[i + 1].end - t[i + 1].start,
JSON_STRING + t[i + 1].start);
i++;
}
else if (jsoneq(JSON_STRING, &t[i], "admin") == 0)
{
/* We may additionally check if the value is either "true" or "false" */
printf("- Admin: %.*s\n", t[i + 1].end - t[i + 1].start,
JSON_STRING + t[i + 1].start);
i++;
}
else if (jsoneq(JSON_STRING, &t[i], "uid") == 0)
{
/* We may want to do strtol() here to get numeric value */
printf("- UID: %.*s\n", t[i + 1].end - t[i + 1].start,
JSON_STRING + t[i + 1].start);
i++;
}
else if (jsoneq(JSON_STRING, &t[i], "groups") == 0)
{
int j;
printf("- Groups:\n");
if (t[i + 1].type != JSMN_ARRAY)
{
continue; /* We expect groups to be an array of strings */
}
for (j = 0; j < t[i + 1].size; j++)
{
jsmntok_t *g = &t[i + j + 2];
printf(" * %.*s\n", g->end - g->start, JSON_STRING + g->start);
}
i += t[i + 1].size + 1;
}
else
{
printf("Unexpected key: %.*s\n", t[i].end - t[i].start,
JSON_STRING + t[i].start);
}
}
解析結果
編譯、下載下傳到開發闆,使用序列槽助手進行測試:
記憶體對比
jsmn設計思想解讀
jsmn對json資料項的抽象
jsmn對json資料中的每一個資料段都會抽象為一個結構體,稱之為token,此結構體非常簡潔:
/**
* JSON token description.
* type type (object, array, string etc.)
* start start position in JSON data string
* end end position in JSON data string
*/
typedef struct jsmntok {
jsmntype_t type;
int start;
int end;
int size;
#ifdef JSMN_PARENT_LINKS
int parent;
#endif
} jsmntok_t;
在本實驗中未開啟JSMN_PARENT_LINKS,是以此結構體占用16Byte大小。
從結構體中的資料成員可以看出,jsmn并不儲存任何具體的資料内容,僅僅記錄:
- 資料項的類型
- 資料項資料段在原始json資料中的起始位置
- 資料項資料段在原始json資料中的結束位置
其中,資料項的類型支援4種:
/**
* JSON type identifier. Basic types are:
* o Object
* o Array
* o String
* o Other primitive: number, boolean (true/false) or null
*/
typedef enum {
JSMN_UNDEFINED = 0,
JSMN_OBJECT = 1,
JSMN_ARRAY = 2,
JSMN_STRING = 3,
JSMN_PRIMITIVE = 4
} jsmntype_t;
jsmn如何解析出每個token
上述說到jsmn将每一個json資料段都抽象為一個token,那麼jsmn是如何對整段json資料進行解析,得到每一個資料項的token呢?
jsmn解析器也是非常簡潔的一個結構體:
/**
* JSON parser. Contains an array of token blocks available. Also stores
* the string being parsed now and current position in that string.
*/
typedef struct jsmn_parser {
unsigned int pos; /* offset in the JSON string */
unsigned int toknext; /* next token to allocate */
int toksuper; /* superior token node, e.g. parent object or array */
} jsmn_parser;
jsmn解析就是将json資料逐個字元進行解析,用pos資料成員來記錄解析器目前的位置,當尋找到特殊字元時,就去之前我們定義的token數組(t)中申請一個空的token成員,将該token在數組中的位置記錄在資料成員toknext中。
源碼在下面的函數中,代碼過多,暫且先不放:
JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len,
jsmntok_t *tokens, const unsigned int num_tokens);
下面用一個執行個體來看看token是怎麼配置設定的。
縮短json原始資料:
static const char *JSON_STRING =
"{\"name\":\"mculover666\",\"admin\":false,\"uid\":1000}";
在解析之後将每個token列印出來:
printf("[type][start][end][size]\n");
for(i = 0;i < r; i++)
{
printf("[%4d][%5d][%3d][%4d]\n", t[i].type, t[i].start, t[i].end, t[i].size);
}
結果如下:
這段json資料解析出的token有7個:
① Object類型的token:{\"name\":\"mculover666\",\"admin\":false,\"uid\":1000}
② String類型的token:"name"、"mculover666"、"admin"、"uid"
③ Primitive類型的token:數字1000,布爾值false
使用者如何從token中提取值
在解析完畢獲得這些token之後,需要根據token數量來判斷是否解析成功:
① 傳回的token數量<0:證明解析失敗,傳回值代表了錯誤類型:
enum jsmnerr {
/* Not enough tokens were provided */
JSMN_ERROR_NOMEM = -1,
/* Invalid character inside JSON string */
JSMN_ERROR_INVAL = -2,
/* The string is not a full JSON packet, more bytes expected */
JSMN_ERROR_PART = -3
};
② 判斷第0個token是否是JSMN_OBJECT類型,如果不是,則證明解析錯誤。
③ 如果token數量大于1,則從第1個token開始判斷字元串是否與給定的鍵值對的名稱相等,若相等,則提取下一個token的内容作為該鍵值對的值。
原文連結:https://mp.weixin.qq.com/s/EyYO48JB4jLOO0xU_dVqEw
轉載自:嵌入式大雜燴
原文連結:推薦一個資源占用極少的json解析器!
本文來源網絡,免費傳達知識,版權歸原作者所有。如涉及作品版權問題,請聯系我進行删除。