/** * \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 * Version: v1.7.0 */ #ifndef LWJSON_HDR_H #define LWJSON_HDR_H #include #include #include "lwjson_opts.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 */