| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388 |
- /**
- * \file lwjson.h
- * \brief LwJSON - Lightweight JSON format parser
- */
- /*
- * Copyright (c) 2024 Tilen MAJERLE
- *
- * Permission is hereby granted, free of charge, to any person
- * obtaining a copy of this software and associated documentation
- * files (the "Software"), to deal in the Software without restriction,
- * including without limitation the rights to use, copy, modify, merge,
- * publish, distribute, sublicense, and/or sell copies of the Software,
- * and to permit persons to whom the Software is furnished to do so,
- * subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be
- * included in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
- * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
- * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
- * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
- * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
- * OTHER DEALINGS IN THE SOFTWARE.
- *
- * This file is part of LwJSON - Lightweight JSON format parser.
- *
- * Author: Tilen MAJERLE <tilen@majerle.eu>
- * Version: v1.8.1
- */
- #ifndef LWJSON_HDR_H
- #define LWJSON_HDR_H
- #include <stdint.h>
- #include <string.h>
- #include "lwjson_opt.h"
- #ifdef __cplusplus
- extern "C" {
- #endif /* __cplusplus */
- /**
- * \defgroup LWJSON Lightweight JSON format parser
- * \brief LwJSON - Lightweight JSON format parser
- * \{
- */
- /**
- * \brief Get size of statically allocated array
- * \param[in] x: Object to get array size of
- * \return Number of elements in array
- */
- #define LWJSON_ARRAYSIZE(x) (sizeof(x) / sizeof((x)[0]))
- /**
- * \brief List of supported JSON types
- */
- typedef enum {
- LWJSON_TYPE_STRING, /*!< String/Text format. Everything that has beginning and ending quote character */
- LWJSON_TYPE_NUM_INT, /*!< Number type for integer */
- LWJSON_TYPE_NUM_REAL, /*!< Number type for real number */
- LWJSON_TYPE_OBJECT, /*!< Object data type */
- LWJSON_TYPE_ARRAY, /*!< Array data type */
- LWJSON_TYPE_TRUE, /*!< True boolean value */
- LWJSON_TYPE_FALSE, /*!< False boolean value */
- LWJSON_TYPE_NULL, /*!< Null value */
- } lwjson_type_t;
- #if defined(LWJSON_DEV)
- extern const char* const lwjson_type_strings[];
- #endif
- /**
- * \brief Real data type
- */
- typedef LWJSON_CFG_REAL_TYPE lwjson_real_t;
- /**
- * \brief Integer data type
- */
- typedef LWJSON_CFG_INT_TYPE lwjson_int_t;
- /**
- * \brief JSON token
- */
- typedef struct lwjson_token {
- struct lwjson_token* next; /*!< Next token on a list */
- lwjson_type_t type; /*!< Token type */
- const char* token_name; /*!< Token name (if exists) */
- size_t token_name_len; /*!< Length of token name (this is needed to support const input strings to parse) */
- union {
- struct {
- const char* token_value; /*!< Pointer to the beginning of the string */
- size_t
- token_value_len; /*!< Length of token value (this is needed to support const input strings to parse) */
- } str; /*!< String data */
- lwjson_real_t num_real; /*!< Real number format */
- lwjson_int_t num_int; /*!< Int number format */
- struct lwjson_token* first_child; /*!< First children object for object or array type */
- } u; /*!< Union with different data types */
- } lwjson_token_t;
- /**
- * \brief JSON result enumeration
- */
- typedef enum {
- lwjsonOK = 0x00, /*!< Function returns successfully */
- lwjsonERR, /*!< Generic error message */
- lwjsonERRJSON, /*!< Error JSON format */
- lwjsonERRMEM, /*!< Memory error */
- lwjsonERRPAR, /*!< Parameter error */
- lwjsonSTREAMWAITFIRSTCHAR, /*!< Streaming parser did not yet receive first valid character
- indicating start of JSON sequence */
- lwjsonSTREAMDONE, /*!< Streaming parser is done,
- closing character matched the stream opening one */
- lwjsonSTREAMINPROG, /*!< Stream parsing is still in progress */
- } lwjsonr_t;
- /**
- * \brief LwJSON instance
- */
- typedef struct {
- lwjson_token_t* tokens; /*!< Pointer to array of tokens */
- size_t tokens_len; /*!< Size of all tokens */
- size_t next_free_token_pos; /*!< Position of next free token instance */
- lwjson_token_t first_token; /*!< First token on a list */
- struct {
- uint8_t parsed : 1; /*!< Flag indicating JSON parsing has finished successfully */
- } flags; /*!< List of flags */
- } lwjson_t;
- lwjsonr_t lwjson_init(lwjson_t* lwobj, lwjson_token_t* tokens, size_t tokens_len);
- lwjsonr_t lwjson_parse_ex(lwjson_t* lwobj, const void* json_data, size_t len);
- lwjsonr_t lwjson_parse(lwjson_t* lwobj, const char* json_str);
- const lwjson_token_t* lwjson_find(lwjson_t* lwobj, const char* path);
- const lwjson_token_t* lwjson_find_ex(lwjson_t* lwobj, const lwjson_token_t* token, const char* path);
- lwjsonr_t lwjson_free(lwjson_t* lwobj);
- void lwjson_print_token(const lwjson_token_t* token);
- void lwjson_print_json(const lwjson_t* lwobj);
- /**
- * \brief Object type for streaming parser
- */
- typedef enum {
- LWJSON_STREAM_TYPE_NONE, /*!< No entry - not used */
- LWJSON_STREAM_TYPE_OBJECT, /*!< Object indication */
- LWJSON_STREAM_TYPE_OBJECT_END, /*!< Object end indication */
- LWJSON_STREAM_TYPE_ARRAY, /*!< Array indication */
- LWJSON_STREAM_TYPE_ARRAY_END, /*!< Array end indication */
- LWJSON_STREAM_TYPE_KEY, /*!< Key string */
- LWJSON_STREAM_TYPE_STRING, /*!< Strin type */
- LWJSON_STREAM_TYPE_TRUE, /*!< True primitive */
- LWJSON_STREAM_TYPE_FALSE, /*!< False primitive */
- LWJSON_STREAM_TYPE_NULL, /*!< Null primitive */
- LWJSON_STREAM_TYPE_NUMBER, /*!< Generic number */
- } lwjson_stream_type_t;
- /**
- * \brief Stream parsing stack object
- */
- typedef struct {
- lwjson_stream_type_t type; /*!< Streaming type - current value */
- union {
- char name[LWJSON_CFG_STREAM_KEY_MAX_LEN
- + 1]; /*!< Last known key name, used only for \ref LWJSON_STREAM_TYPE_KEY type */
- uint16_t index; /*!< Current index when type is an array */
- } meta; /*!< Meta information */
- } lwjson_stream_stack_t;
- typedef enum {
- LWJSON_STREAM_STATE_WAITINGFIRSTCHAR = 0x00, /*!< State to wait for very first opening character */
- LWJSON_STREAM_STATE_PARSING, /*!< In parsing of the first char state - detecting next character state */
- LWJSON_STREAM_STATE_PARSING_STRING, /*!< Parse string primitive */
- LWJSON_STREAM_STATE_PARSING_PRIMITIVE, /*!< Parse any primitive that is non-string, either "true", "false", "null" or a number */
- LWJSON_STREAM_STATE_EXPECTING_COMMA_OR_END, /*!< Expecting ',', '}' or ']' */
- LWJSON_STREAM_STATE_EXPECTING_COLON, /*!< Expecting ':' */
- } lwjson_stream_state_t;
- /* Forward declaration */
- struct lwjson_stream_parser;
- /**
- * \brief Callback function for various events
- *
- */
- typedef void (*lwjson_stream_parser_callback_fn)(struct lwjson_stream_parser* jsp, lwjson_stream_type_t type);
- /**
- * \brief LwJSON streaming structure
- */
- typedef struct lwjson_stream_parser {
- lwjson_stream_stack_t
- stack[LWJSON_CFG_STREAM_STACK_SIZE]; /*!< Stack used for parsing. TODO: Add conditional compilation flag */
- size_t stack_pos; /*!< Current stack position */
- lwjson_stream_state_t parse_state; /*!< Parser state */
- lwjson_stream_parser_callback_fn evt_fn; /*!< Event function for user */
- void* user_data; /*!< User data for callback function */
- /* State */
- union {
- struct {
- char buff[LWJSON_CFG_STREAM_STRING_MAX_LEN
- + 1]; /*!< Buffer to write temporary data. TODO: Size to be variable with define */
- size_t buff_pos; /*!< Buffer position for next write (length of bytes in buffer) */
- size_t buff_total_pos; /*!< Total buffer position used up to now (in several data chunks) */
- uint8_t is_last; /*!< Status indicates if this is the last part of the string */
- } str; /*!< String structure. It is only used for keys and string objects.
- Use primitive part for all other options */
- struct {
- char buff[LWJSON_CFG_STREAM_PRIMITIVE_MAX_LEN + 1]; /*!< Temporary write buffer */
- size_t buff_pos; /*!< Buffer position for next write */
- } prim; /*!< Primitive object. Used for all types, except key or string */
- /* Todo: Add other types */
- } data; /*!< Data union used to parse various */
- char prev_c; /*!< History of characters */
- } lwjson_stream_parser_t;
- lwjsonr_t lwjson_stream_init(lwjson_stream_parser_t* jsp, lwjson_stream_parser_callback_fn evt_fn);
- lwjsonr_t lwjson_stream_set_user_data(lwjson_stream_parser_t* jsp, void* user_data);
- void* lwjson_stream_get_user_data(lwjson_stream_parser_t* jsp);
- lwjsonr_t lwjson_stream_reset(lwjson_stream_parser_t* jsp);
- lwjsonr_t lwjson_stream_parse(lwjson_stream_parser_t* jsp, char c);
- /**
- * \brief Get number of tokens used to parse JSON
- * \param[in] lwobj: Pointer to LwJSON instance
- * \return Number of tokens used to parse JSON
- */
- #define lwjson_get_tokens_used(lwobj) (((lwobj) != NULL) ? ((lwobj)->next_free_token_pos + 1) : 0)
- /**
- * \brief Get very first token of LwJSON instance
- * \param[in] lwobj: Pointer to LwJSON instance
- * \return Pointer to first token
- */
- #define lwjson_get_first_token(lwobj) (((lwobj) != NULL) ? (&(lwobj)->first_token) : NULL)
- /**
- * \brief Get token value for \ref LWJSON_TYPE_NUM_INT type
- * \param[in] token: token with integer type
- * \return Int number if type is integer, `0` otherwise
- */
- #define lwjson_get_val_int(token) \
- ((lwjson_int_t)(((token) != NULL && (token)->type == LWJSON_TYPE_NUM_INT) ? (token)->u.num_int : 0))
- /**
- * \brief Get token value for \ref LWJSON_TYPE_NUM_REAL type
- * \param[in] token: token with real type
- * \return Real numbeer if type is real, `0` otherwise
- */
- #define lwjson_get_val_real(token) \
- ((lwjson_real_t)(((token) != NULL && (token)->type == LWJSON_TYPE_NUM_REAL) ? (token)->u.num_real : 0))
- /**
- * \brief Get first child token for \ref LWJSON_TYPE_OBJECT or \ref LWJSON_TYPE_ARRAY types
- * \param[in] token: token with integer type
- * \return Pointer to first child or `NULL` if parent token is not object or array
- */
- #define lwjson_get_first_child(token) \
- (const void*)(((token) != NULL && ((token)->type == LWJSON_TYPE_OBJECT || (token)->type == LWJSON_TYPE_ARRAY)) \
- ? (token)->u.first_child \
- : NULL)
- /**
- * \brief Get string value from JSON token
- * \param[in] token: Token with string type
- * \param[out] str_len: Pointer to variable holding length of string.
- * Set to `NULL` if not used
- * \return Pointer to string or `NULL` if invalid token type
- */
- static inline const char*
- lwjson_get_val_string(const lwjson_token_t* token, size_t* str_len) {
- if (token != NULL && token->type == LWJSON_TYPE_STRING) {
- if (str_len != NULL) {
- *str_len = token->u.str.token_value_len;
- }
- return token->u.str.token_value;
- }
- return NULL;
- }
- /**
- * \brief Get length of string for \ref LWJSON_TYPE_STRING token type
- * \param[in] token: token with string type
- * \return Length of string in units of bytes
- */
- #define lwjson_get_val_string_length(token) \
- ((size_t)(((token) != NULL && (token)->type == LWJSON_TYPE_STRING) ? (token)->u.str.token_value_len : 0))
- /**
- * \brief Compare string token with user input string for a case-sensitive match
- * \param[in] token: Token with string type
- * \param[in] str: NULL-terminated string to compare
- * \return `1` if equal, `0` otherwise
- */
- static inline uint8_t
- lwjson_string_compare(const lwjson_token_t* token, const char* str) {
- if (token != NULL && token->type == LWJSON_TYPE_STRING) {
- return strncmp(token->u.str.token_value, str, token->u.str.token_value_len) == 0;
- }
- return 0;
- }
- /**
- * \brief Compare string token with user input string for a case-sensitive match
- * \param[in] token: Token with string type
- * \param[in] str: NULL-terminated string to compare
- * \param[in] len: Length of the string in bytes
- * \return `1` if equal, `0` otherwise
- */
- static inline uint8_t
- lwjson_string_compare_n(const lwjson_token_t* token, const char* str, size_t len) {
- if (token != NULL && token->type == LWJSON_TYPE_STRING && len <= token->u.str.token_value_len) {
- return strncmp(token->u.str.token_value, str, len) == 0;
- }
- return 0;
- }
- /**
- * \name LWJSON_STREAM_SEQ
- * \brief Helper functions for stack analysis in a callback function
- * \note Useful exclusively for streaming functions
- * \{
- */
- /**
- * \brief Check the sequence of JSON stack, starting from start_number index
- * \note This applies only to one sequence element. Other macros, starting with
- * `lwjson_stack_seq_X` (where X is the sequence length), provide
- * more parameters for longer sequences.
- *
- * \param[in] jsp: LwJSON stream instance
- * \param[in] start_num: Start number in the stack. Typically starts with `0`, but user may choose another
- * number, if intention is to check partial sequence only
- * \param[in] sp0: Stream stack type. Value of \ref lwjson_stream_type_t, but only last part of the enum.
- * If user is interested in the \ref LWJSON_STREAM_TYPE_OBJECT,
- * you should only write `OBJECT` as parameter.
- * Idea behind is to make code smaller and easier to read, especially when
- * using other sequence values with more parameters.
- * \return `0` if sequence doesn't match, non-zero otherwise
- */
- #define lwjson_stack_seq_1(jsp, start_num, sp0) ((jsp)->stack[(start_num)].type == LWJSON_STREAM_TYPE_##sp0)
- #define lwjson_stack_seq_2(jsp, start_num, sp0, sp1) \
- (lwjson_stack_seq_1((jsp), (start_num) + 0, sp0) && lwjson_stack_seq_1((jsp), (start_num) + 1, sp1))
- #define lwjson_stack_seq_3(jsp, start_num, sp0, sp1, sp2) \
- (lwjson_stack_seq_1((jsp), (start_num) + 0, sp0) && lwjson_stack_seq_2((jsp), (start_num) + 1, sp1, sp2))
- #define lwjson_stack_seq_4(jsp, start_num, sp0, sp1, sp2, sp3) \
- (lwjson_stack_seq_1((jsp), (start_num) + 0, sp0) && lwjson_stack_seq_3((jsp), (start_num) + 1, sp1, sp2, sp3))
- #define lwjson_stack_seq_5(jsp, start_num, sp0, sp1, sp2, sp3, sp4) \
- (lwjson_stack_seq_1((jsp), (start_num) + 0, sp0) && lwjson_stack_seq_4((jsp), (start_num) + 1, sp1, sp2, sp3, sp4))
- #define lwjson_stack_seq_6(jsp, start_num, sp0, sp1, sp2, sp3, sp4, sp5) \
- (lwjson_stack_seq_1((jsp), (start_num) + 0, sp0) \
- && lwjson_stack_seq_5((jsp), (start_num) + 1, sp1, sp2, sp3, sp4, sp5))
- #define lwjson_stack_seq_7(jsp, start_num, sp0, sp1, sp2, sp3, sp4, sp5, sp6) \
- (lwjson_stack_seq_1((jsp), (start_num) + 0, sp0) \
- && lwjson_stack_seq_6((jsp), (start_num) + 1, sp1, sp2, sp3, sp4, sp5, sp6))
- #define lwjson_stack_seq_8(jsp, start_num, sp0, sp1, sp2, sp3, sp4, sp5, sp6, sp7) \
- (lwjson_stack_seq_1((jsp), (start_num) + 0, sp0) \
- && lwjson_stack_seq_7((jsp), (start_num) + 1, sp1, sp2, sp3, sp4, sp5, sp6, sp7))
- /**
- * \}
- */
- /**
- * \}
- */
- #ifdef __cplusplus
- }
- #endif /* __cplusplus */
- #endif /* LWJSON_HDR_H */
|