一、简介
1.1 jsmn
基于 C 语言比较有名的 JSON 格式实现的方法,即:jsmn 和 cJSON 。
这两个协议,jsmn 特别适用于单片机中存储空间极其有限的环境,一个资源占用极小的 JSON 解析器,号称世界上最快;cJSON 适合空间比较充足,需要大型数据处理的环境。
jsmn主要有以下特性:
- 没有任何库依赖关系;
- 语法与C89兼容,代码可移植性高;
- 没有任何动态内存分配
- 极小的代码占用
- API只有两个,极其简洁
1.2 JSON
JSON(JavaScript Object Notation, JS 对象简谱)是一种轻量级的数据交换格式。它基于 ECMAScript (欧洲计算机协会制定的js规范)的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。
1.2.1 JSON 语法规则
在 JS 语言中,一切都是对象。因此,任何支持的类型都可以通过 JSON 来表示,例如字符串、数字、对象、数组等。但是对象和数组是比较特殊且常用的两种类型:
● 对象表示为键值对
● 数据由逗号分隔
● 花括号保存对象
● 方括号保存数组
1.2.2 JSON 键/值对
JSON 键值对是用来保存 JS 对象的一种方式,键/值对组合中的键名写在前面并用双引号 "" 包裹,使用冒号 : 分隔,然后紧接着值:
{"firstName": "Json"}
二、移植
项目地址:https://github.com/zserge/jsmn
将 jsmn.h 加入工程中
2.1 包含jsmn头文件
使用时包含头文件,因为jsmn的函数定义也是在头文件中,所以第一次添加的时候,可以直接添加:
/* USER CODE BEGIN Includes */
#include "jsmn.h"
#include //用于printf打印
#include //用于字符串处理
/* USER CODE END Includes */
已经使用过之后,在别的文件中继续使用时,需要这样添加,且顺序不可互换:
/* USER CODE BEGIN 0 */
#define JSMN_HEADER
#include "jsmn.h"
/* USER CODE END 0 */
否则会造成函数重定义:
三、API
3.1 jsmn_init
初始化解析器
3.2 jsmn_parse
解析数据,获取 token
3.3 token结构
jsmn 将每一个 json 数据段都抽象为一个 token:
- 数据项的类型
- 数据项数据段在原始 json 数据中的起始位置
- 数据项数据段在原始 json 数据中的结束位置
typedef struct {
jsmntype_t type; // Token type
int start; // Token start position
int end; // Token end position
int size; // Number of child (nested) tokens
} jsmntok_t;
json 原始数据:
{"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
3.4 token类型
typedef enum {
JSMN_UNDEFINED = 0,
JSMN_OBJECT = 1,
JSMN_ARRAY = 2,
JSMN_STRING = 3,
JSMN_PRIMITIVE = 4
} jsmntype_t;
四、示例1
json 原始数据:
{"user":"johndoe", "admin":false, "uid":1000, "groups": ["users", "wheel", "audio", "video"]}
#include "jsmn.h"
#include
#include
#include
// 1.原始json格式的string序列
static const char *JSON_STRING =
"{\"user\": \"johndoe\", \"admin\": false, \"uid\": 1000,\n "
"\"groups\": [\"users\", \"wheel\", \"audio\", \"video\"]}";
// 2.编写在原始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;
}
int main() {
int i;
int r;
// 3.创建JSON解析器p,存储JSON中数据位置信息
jsmn_parser p;
// 4.假定最多有128个符号,创建其描述类型
jsmntok_t t[128]; /* We expect no more than 128 tokens */
// 5.在一组符号上创建JSON解释器,初始化
jsmn_init(&p);
// 6.运行JSON解释器,将一个JSON数据字符串转换为一个符号数组,每个数组描述一个JSON对象
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;
}
// 7.获取JSON中的对象数据
/* 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);
}
}
return EXIT_SUCCESS;
}
五、示例2
json 原始数据:
{"proto":"static", "ipaddr":"192.168.2.67", "netmask":"255.255.255.0", "gateway":"192.168.2.1", "dns":"192.168.2.1"}
{"proto":"dhcp"}
jsmn_parser parser;
jsmntok_t jsonTokens[128];
int numTokens;
jsmn_init(&parser);
numTokens = jsmn_parse(&parser, recvMsgBuff, strlen(recvMsgBuff), jsonTokens, 128);
if(numTokens < 0)
{
printf("Failed to parse JSON (%d)!\n", numTokens);
}
else
{
int i;
for(i = 1; i < numTokens; i++)
{
if(jsoneq(recvMsgBuff, &jsonTokens[i], "proto") == 0)
{
i++;
if(jsoneq(recvMsgBuff, &jsonTokens[i], "static") == 0) // 静态IP
{
printf("proto: static\n");
i++;
if(jsoneq(recvMsgBuff, &jsonTokens[i], "ipaddr") == 0)
{
char ipaddr[20] = {0};
memcpy(ipaddr, recvMsgBuff + jsonTokens[i + 1].start, jsonTokens[i + 1].end - jsonTokens[i + 1].start);
printf("ipaddr: %s\n", ipaddr);
i += 2;
}
if(jsoneq(recvMsgBuff, &jsonTokens[i], "netmask") == 0)
{
char netmask[20] = {0};
memcpy(netmask, recvMsgBuff + jsonTokens[i + 1].start, jsonTokens[i + 1].end - jsonTokens[i + 1].start);
printf("netmask: %s\n", netmask);
i += 2;
}
if(jsoneq(recvMsgBuff, &jsonTokens[i], "gateway") == 0)
{
char gateway[20] = {0};
memcpy(gateway, recvMsgBuff + jsonTokens[i + 1].start, jsonTokens[i + 1].end - jsonTokens[i + 1].start);
printf("gateway: %s\n", gateway);
i += 2;
}
if(jsoneq(recvMsgBuff, &jsonTokens[i], "dns") == 0)
{
char dns[20] = {0};
memcpy(dns, recvMsgBuff + jsonTokens[i + 1].start, jsonTokens[i + 1].end - jsonTokens[i + 1].start);
printf("dns: %s\n", dns);
}
}
else if(jsoneq(recvMsgBuff, &jsonTokens[i], "dhcp") == 0) // 动态IP
{
printf("proto: dhcp\n");
}
}
else
{
printf("Unexpected key: %.*s\n", jsonTokens[i].end - jsonTokens[i].start, recvMsgBuff + jsonTokens[i].start);
}
}
}
• 由 Leung 写于 2020 年 9 月 17 日
• 参考:jsmn | 一个资源占用极小,解析速度最快的json解析器
嵌入式中JSON数据格式实现—JSMN