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

你可能感兴趣的:(jsmn学习笔记 —— 资源占用极小,解析速度最快的JSON解析器)