jsmn示例使用

发布时间 2023-12-05 17:24:06作者: USTHzhanglu

前言

jsmn是一款超级精简的c语言json解释器,用于嵌入式进行json数据解析特别友好。
官方库: https://github.com/zserge/jsmn/tree/master

jsmn (pronounced like 'jasmine') is a minimalistic JSON parser in C. It can be easily integrated into resource-limited or embedded projects.

Features

  • compatible with C89
  • no dependencies (even libc!)
  • highly portable (tested on x86/amd64, ARM, AVR)
  • about 200 lines of code
  • extremely small code footprint
  • API contains only 2 functions
  • no dynamic memory allocation
  • incremental single-pass parsing
  • library code is covered with unit-tests

导入

jsmn导入非常简单,只需要将jsmn.h加入工程即可。
新建一个json.c,引入jsmn.h

#include "jsmn.h"
#define LOG_JSON        printf //use which you need

jsmn最基础使用只需要两个API:

  1. jsmn_init 使用给定的tokens数组初始化json解释器;
  2. jsmn_parse 解析给定的字符串,将解析结果存入tokens数组;

官方example

#include "../jsmn.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/*
 * A small example of jsmn parsing when JSON structure is known and number of
 * tokens is predictable.
 */

static const char *JSON_STRING =
    "{\"user\": \"johndoe\", \"admin\": false, \"uid\": 1000,\n  "
    "\"groups\": [\"users\", \"wheel\", \"audio\", \"video\"]}";

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;
  jsmn_parser p;
  jsmntok_t t[128]; /* We expect no more than 128 tokens */

  jsmn_init(&p);
  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;
  }

  /* 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;
}

看起来很繁琐,但是本质上只执行了 数据解析、数据对比、数据打印三个动作。
在使用jsmn前,先分析下jsmn_parse的返回值。

A non-negative return value of jsmn_parse is the number of tokens actually used by the parser. Passing NULL instead of the tokens array would not store parsing results, but instead the function will return the number of tokens needed to parse the given string. This can be useful if you don't know yet how many tokens to allocate.

If something goes wrong, you will get an error. Error will be one of these:

JSMN_ERROR_INVAL - bad token, JSON string is corrupted
JSMN_ERROR_NOMEM - not enough tokens, JSON string is too large
JSMN_ERROR_PART - JSON string is too short, expecting more JSON data

该函数返回一个int值,当该值非负时,该值表示token的数量,为负时标识json解析错误。

jsmntok_t

token is an object of jsmntok_t type:

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;

jsmn并没有在c上实现哈希表,只是简单的通过token,储存json对象在字符串中的位置。
token的结果打印出来:

  jsmn_parser p;
  jsmntok_t t[128] = {0}; /* We expect no more than 128 JSON tokens */
  jsmn_init(&p);
  int r = jsmn_parse(&p,JSON_STRING, strlen(JSON_STRING), t, 128); // "s" is the char array holding the json content
  
  LOG_JSON("[index][type][start][end][size]\n");
  for(int i = 0;i < r; i++)
  {
      LOG_JSON("[%5d][%4d][%5d][%3d][%4d]\n",i, t[i].type, t[i].start, t[i].end, t[i].size);
  }
  return;

输出结果如下:

[index][type][start][end][size]
[    0][   1][    0][ 98][   4]
[    1][   4][    2][  6][   1]
[    2][   4][   10][ 17][   0]
[    3][   4][   21][ 26][   1]
[    4][   8][   29][ 34][   0]
[    5][   4][   37][ 40][   1]
[    6][   8][   43][ 47][   0]
[    7][   4][   52][ 58][   1]
[    8][   2][   61][ 97][   4]
[    9][   4][   63][ 68][   0]
[   10][   4][   72][ 77][   0]
[   11][   4][   81][ 86][   0]
[   12][   4][   90][ 95][   0]

type表示该token对应的类型:

Token types are described by jsmntype_t:

typedef enum {
  JSMN_UNDEFINED = 0,
  JSMN_OBJECT = 1 << 0,
  JSMN_ARRAY = 1 << 1,
  JSMN_STRING = 1 << 2,
  JSMN_PRIMITIVE = 1 << 3
} jsmntype_t;

start 表示该对象在字符串中的起始索引;
stop 表示该对象在字符串中的结束索引;
size 表示该对象有多少个子(嵌套)token;
知道了这些,就便于理解输出结果了。

[index][type][start][end][size]
[    0][   1][    0][ 98][   4]

表示在传入字符串的[0,98]内存在一个对象,含有四个成员;
对比字符串,可以很容易知道这四个成员分别是

user:
admin:
uid:
groups:
[index][type][start][end][size]
[    1][   4][    2][  6][   1]

表示在传入字符串的[2,6]内存在一个字符串,含有一个成员,即存在一个键值对。

[index][type][start][end][size]
[    2][   4][   10][ 17][   0]

表示在传入字符串的[10,17]内存在一个字符串,不含有任何成员,因为它实际上是一个键的值。

数据解析

知道了token的具体含义,我们很容易将json中的键对值取出:

  for(int i = 0;i < r; i++)
  {
    if(t[i].type == JSMN_STRING && t[i].size!=0)
    {
      LOG_JSON("%.*s:", t[i].end - t[i].start,
             JSON_STRING + t[i].start);
      LOG_JSON("%.*s\n", t[i + 1].end - t[i + 1].start,
             JSON_STRING + t[i + 1].start);
      i++;
    }
  }

结果如下:

user:johndoe
admin:false
uid:1000
groups:["users", "wheel", "audio", "video"]

jsmn支持json嵌套,即key的值可以是json 对象,只需要按照token的顺序继续将其取出即可。

此时回看官方example,可以发现其通过jsoneq对json的键进行强匹配,然后输出对应的值