From f6469c6f923ac6e4216182cbb2a137a9c00dbf6c Mon Sep 17 00:00:00 2001 From: Natie Date: Sun, 22 Oct 2023 16:53:31 +0200 Subject: [PATCH] New JSMN_PARENT_CNT to allow enhanced enumeration of JSON Structures. Update parentcnt.c Create parentcnt.json JSON for example Update comments Updated comments Update comments --- example/jsmn_tools.c | 97 ++++++++++++++++++++++ example/jsmn_tools.h | 121 ++++++++++++++++++++++++++++ example/parentcnt.c | 178 +++++++++++++++++++++++++++++++++++++++++ example/parentcnt.json | 22 +++++ jsmn.h | 22 +++++ 5 files changed, 440 insertions(+) create mode 100644 example/jsmn_tools.c create mode 100644 example/jsmn_tools.h create mode 100644 example/parentcnt.c create mode 100644 example/parentcnt.json diff --git a/example/jsmn_tools.c b/example/jsmn_tools.c new file mode 100644 index 00000000..e5cb979c --- /dev/null +++ b/example/jsmn_tools.c @@ -0,0 +1,97 @@ +#define JSMN_STRICT +#define JSMN_PARENT_LINKS +#define JSMN_PARENT_CNT +#include "../jsmn.h" +#include "jsmn_tools.h" +#include +#include + + +int +jsmn_eq(const char *json, jsmntok_t *t, const char *s) +{ + if (t->type == JSMN_STRING && (int)strlen(s) == t->end - t->start && + strncmp(json + t->start, s, t->end - t->start) == 0) { + return 0; + } + return -1; +} + +jsmntok_t * +_next(jsmntok_t *t) +{ + return t + 1 + t->size ; +} + +jsmntok_t * +jsmn_get_next(jsmntok_t *first, jsmntok_t *t) +{ + if (t == 0) { + return first ; + } + if (t + t->size < first + first->size) { + return _next(t) ; + } + return 0 ; +} + +int +jsmn_object_enum (const char *js, jsmntok_t *t, size_t count, + jsmn_key_val_t * out, size_t out_count) +{ + jsmntok_t *start = t; + jsmntok_t *key; + jsmntok_t *val; + + if (t->type != JSMN_OBJECT) { + printf("OBJECT EXPECTED!\r\n"); + return -1; + } + + t = t + 1 ; + + while (t < start + count) { + if (t->type == JSMN_STRING) { + key = t ; + val = t + 1 ; + t += 1 ; + + } else if (t->type == JSMN_OBJECT) { + printf("UNEXPECTED: OBJECT\r\n"); + return -1; ; + } else if (t->type == JSMN_PRIMITIVE) { + printf("UNEXPECTED: '%.*s'\r\n", t->end - t->start, js + t->start); + return -1; + } else { + printf("UNEXPECTED:\r\n"); + return -1; + } + +#ifdef DEBUG + printf("KEY: '%.*s': ", key->end - key->start, js + key->start); + + if ((val->type == JSMN_STRING) || (val->type == JSMN_PRIMITIVE)) { + printf("VAL: '%.*s'\r\n", val->end - val->start, js + val->start); + } else { + printf("VAL: '%s\r\n", (val->type == JSMN_OBJECT) ? "OBJECT" : "ARRAY"); + } +#endif + + int i ; + for (i=0; iend - t->start, js + t->start) ; +} diff --git a/example/jsmn_tools.h b/example/jsmn_tools.h new file mode 100644 index 00000000..c960bb6e --- /dev/null +++ b/example/jsmn_tools.h @@ -0,0 +1,121 @@ +/** + * @file jsmn_tools.h + * + * @brief An Auxiliary Header for JSMN Library Enabling Enhanced JSON + * Structure Enumeration + * + * This header file, designed to complement the jsmn.h library, is tailored + * for use in conjunction with a specially defined JSMN_PARENT_CNT during the + * compilation of jsmn.h. This subtle customisation allows for refined and + * advanced JSON structure traversal, making it possible to iteratively explore + * nested sub-objects within the JSON hierarchy. + * + */ + +#ifndef __JSNM_TOOLS_H__ +#define __JSNM_TOOLS_H__ + +#define JSMN_HEADER +#define JSMN_PARENT_LINKS +#define JSMN_PARENT_CNT +#include "../jsmn.h" + + +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * Structure for representing a key-value pair in a JSON object. + * + * This structure is used to associate a JSON object key (a string) with its + * corresponding value (a jsmntok_t token). It is employed to store and manage + * key-value pairs extracted from a JSON object during parsing. + * + * @param key A pointer to the key string. + * @param value A pointer to the jsmntok_t token of the value. + */ +typedef struct jsmn_key_val_s { + const char * key ; + jsmntok_t * value ; +} jsmn_key_val_t ; + +/** + * Enumerate and extract key-value pairs from a JSON object. + * + * This function takes a JSON object represented by a jsmntok_t pointer + * and extracts its key-value pairs. It then updates an array of jsmn_key_val_t + * structures, associating the keys with their corresponding values. + * + * This function will not traverse nested structures or arrays. + * + * + * @param js A pointer to the JSON string. + * @param t A pointer to the JSON object token. + * @param count The number of tokens in the JSON object. + * @param out An array of jsmn_key_val_t structures to store the key-value pairs. + * @param out_count The size of the 'out' array. + * + * @return 0 if successful, -1 if an error occurs (e.g., unexpected JSON structure). + */ +int jsmn_object_enum (const char *js, jsmntok_t *t, size_t count, + jsmn_key_val_t * out, size_t out_count) ; + +/** + * Get the next JSON token in a sequence. + * + * This function takes the current token pointer 't' and returns a pointer + * to the next token in a JSON token sequence. If 't' is NULL, it returns + * the first token in the sequence. + * + * This function will not traverse nested structures or arrays. + * + * @param first A pointer to the first token in the JSON token sequence. + * @param t A pointer to the current token. + * + * @return A pointer to the next token in the sequence or NULL if there are no more tokens. + */ +jsmntok_t * jsmn_get_next(jsmntok_t *first, jsmntok_t *t); + +/** + * Compare a JSON token with a given string. + * + * This function compares the content of a JSON token with a provided string. + * It checks if the token represents a string and if the string's length matches + * the length of the provided string. Additionally, it verifies if the actual content + * of the token matches the provided string. + * + * @param json A pointer to the JSON string. + * @param t A pointer to the JSON token to be compared. + * @param s A pointer to the string for comparison. + * + * @return 0 if the token content matches the provided string, -1 otherwise. + */ +int jsmn_eq(const char *json, jsmntok_t *t, const char *s); + +/** + * Copy the content of a JSON string token to a character buffer. + * + * This function copies the content of a JSON string token to a character buffer. + * It uses snprintf to ensure proper handling of the destination buffer size + * and takes care of extracting the substring based on the token's start and end indices. + * + * @param js A pointer to the JSON string. + * @param t A pointer to the JSON object token. + * @param sz A pointer to the destination buffer. + * @param size The size of the destination buffer. + * + * @return The number of characters written to the destination buffer (excluding the null terminator). + * If the return value is greater than or equal to 'size', it indicates truncation. + */ +int jsmn_copy_str (const char *js, jsmntok_t *t, char * sz, size_t size); + + +#ifdef __cplusplus +} +#endif + +#endif /* __JSNM_TOOLS_H__ */ diff --git a/example/parentcnt.c b/example/parentcnt.c new file mode 100644 index 00000000..cce3a323 --- /dev/null +++ b/example/parentcnt.c @@ -0,0 +1,178 @@ +#include "jsmn_tools.h" +#include +#include +#include +#include +#include +#include + + +static int read_file(const char * filename, char ** pbuffer); + +/** + * Main function showcasing JSON parsing using JSMN with defined JSMN_PARENT_CNT. + * + * This function reads a sample JSON file, utilizes the JSMN library with a + * specifically defined JSMN_PARENT_CNT during compilation, and extracts information + * about operations to execute. The JSON structure includes data such as serial numbers, + * indices, and states for outlets. The provided example JSON is as follows: + * + * { + * "version": "0.1", + * "endpoint": "outlets", + * "cmd": "set", + * "switches": [ + * { + * "serial": "12345", + * "index": [0, 1, 2, 3], + * "state": ["open", "close", "open", "close"] + * }, + * { + * "serial": "67890", + * "index": [0, 1, 2, 3], + * "state": ["open", "open", "open", "close"] + * } + * ], + * "test": { + * "test2": { + * "key": "value" + * } + * } + * } + * + * The expected output would be: + * + * serial 12345: index 0 to open + * serial 12345: index 1 to close + * serial 12345: index 2 to open + * serial 12345: index 3 to close + * serial 67890: index 0 to open + * serial 67890: index 1 to open + * serial 67890: index 2 to open + * serial 67890: index 3 to close + */ +int main() +{ + jsmn_parser p; + jsmntok_t * t ; + jsmn_key_val_t out[] = { {"endpoint", 0}, {"cmd", 0} , {"switches", 0} }; + char * buffer ; + int len = read_file ("parentcnt.json", &buffer) ; + + if (len <= 0) { + return 1; + } + + printf("parsing json...\r\n"); + jsmn_init(&p); + int r = jsmn_parse(&p, buffer, len, 0, 0); // content is the char array holding the json content + + printf("%d objects found!\r\n", r); + if (r <= 0) { + printf("Error invalid json\r\n"); + return 1; + } + + t = malloc (r * sizeof(jsmntok_t)) ; + if (!t) { + printf("Error out of memory\r\n"); + return 1; + } + + jsmn_init(&p); + r = jsmn_parse (&p, buffer, len, t, r) ; + jsmn_object_enum (buffer, t, r, out, 3) ; + printf("\r\n\r\n"); + + jsmntok_t * switches = out[2].value; + if (switches) { + /* A "switches" array is present. + * Assign "next" to the first object in the array. + */ + jsmntok_t * next = switches + 1 ; + + /* Enumerate over all the objects in the array. */ + do { + /* out3 is declared here to clear the `values` of out3 to NULL. + * jsmn_object_enum only assigns a value if it is NULL. + */ + jsmn_key_val_t out3[] = { {"serial", NULL}, {"index", NULL} , + {"state", NULL} } ; + jsmn_object_enum (buffer, next, next->size, out3, 3) ; + jsmntok_t * serial = out3[0].value ; + + if (serial) { + /* The serial key is present so it is a valid object. */ + char szserial[12] ; + /* Extract the two arrays we will use to switch the outlets. */ + jsmntok_t * next_index = out3[1].value + 1 ; + jsmntok_t * next_state = out3[2].value + 1 ; + + jsmn_copy_str (buffer, serial, szserial, 12) ; + + while (next_index && next_state) { + /* Enumerate over all the indices in the arrays and switch the outlets. */ + char szindex[8] ; + char szstate[8] ; + + jsmn_copy_str (buffer, next_index, szindex, 8) ; + jsmn_copy_str (buffer, next_state, szstate, 8) ; + + /* We don't really switch, just print the result. */ + printf("serial %s: index %s to %s\r\n", + szserial, szindex, szstate) ; + + next_index = jsmn_get_next (out3[1].value, next_index) ; + next_state = jsmn_get_next (out3[2].value, next_state) ; + + } + + } + + } while ((next = jsmn_get_next (switches, next))) ; + + } + + /* Don't forget to free the allocated memory when you're done */ + free(buffer); + + return 0; +} + + +int read_file(const char * filename, char ** pbuffer) +{ + FILE *file; + char *buffer; + long len; + + /* Open the file in binary read mode */ + file = fopen(filename, "rb"); + + if (file == NULL) { + printf("Error opening the file"); + return 0; + } + + /* Get the file length */ + fseek(file, 0, SEEK_END); + len = ftell(file); + rewind(file); + + /* Allocate memory for the buffer */ + buffer = (char *)malloc(len); + + if (buffer == NULL) { + printf("Error allocating memory"); + return 0; + } + + /* Read the entire file into the buffer */ + len = fread(buffer, 1, len, file); + /* Close the file */ + fclose(file); + + *pbuffer = buffer; + + return len; +} diff --git a/example/parentcnt.json b/example/parentcnt.json new file mode 100644 index 00000000..e3d05c29 --- /dev/null +++ b/example/parentcnt.json @@ -0,0 +1,22 @@ +{ + "version": "0.1", + "endpoint": "outlets", + "cmd": "set", + "switches": [ + { + "serial": "12345", + "index": [0, 1, 2, 3], + "state": ["open", "close", "open", "close"] + }, + { + "serial": "67890", + "index": [0, 1, 2, 3], + "state": ["open", "open", "open", "close"] + } + ], + "test": { + "test2": { + "key": "value" + } + } +} \ No newline at end of file diff --git a/jsmn.h b/jsmn.h index 8ac14c1b..92f670b5 100644 --- a/jsmn.h +++ b/jsmn.h @@ -296,9 +296,17 @@ JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len, return JSMN_ERROR_INVAL; } #endif +#ifndef JSMN_PARENT_CNT t->size++; +#endif #ifdef JSMN_PARENT_LINKS token->parent = parser->toksuper; +#ifdef JSMN_PARENT_CNT + for (int parent = token->parent; parent>=0 ; + parent = tokens[parent].parent) { + tokens[parent].size++; + } +#endif #endif } token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY); @@ -365,7 +373,14 @@ JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len, } count++; if (parser->toksuper != -1 && tokens != NULL) { +#ifndef JSMN_PARENT_CNT tokens[parser->toksuper].size++; +#else + for (int parent = parser->toksuper; parent>=0 ; + parent = tokens[parent].parent) { + tokens[parent].size++; + } +#endif } break; case '\t': @@ -428,7 +443,14 @@ JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len, } count++; if (parser->toksuper != -1 && tokens != NULL) { +#ifndef JSMN_PARENT_CNT tokens[parser->toksuper].size++; +#else + for (int parent = parser->toksuper; parent>=0 ; + parent = tokens[parent].parent) { + tokens[parent].size++; + } +#endif } break;