jsmn学习笔记 —— 资源占用极小,解析速度最快的JSON解析器

一、简介

1.1 jsmn

基于 C 语言比较有名的 JSON 格式实现的方法,即:jsmncJSON
这两个协议,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 <stdio.h>  //用于printf打印
#include <string.h> //用于字符串处理

/* 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 <stdio.h>
#include <stdlib.h>
#include <string.h>

// 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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335