// SPDX-License-Identifier: LGPL-2.1-or-later /* * Copyright (C) 2021-2024 Cyril Hrubis */ /** * @file ujson_reader.h * @brief A recursive descend JSON parser. * * All the function that parse JSON return zero on success and non-zero on a * failure. Once an error has happened all subsequent attempts to parse more * return with non-zero exit status immediatelly. This is designed so that we * can parse several values without checking each return value and only check * if error has happened at the end of the sequence. */ #ifndef UJSON_READER_H #define UJSON_READER_H #include #include /** * @brief An ujson_reader initializer with default values. * * @param buf A pointer to a buffer with JSON data. * @param buf_len A JSON data buffer lenght. * @param rflags enum ujson_reader_flags. * * @return An ujson_reader initialized with default values. */ #define UJSON_READER_INIT(buf, buf_len, rflags) { \ .max_depth = UJSON_RECURSION_MAX, \ .err_print = UJSON_ERR_PRINT, \ .err_print_priv = UJSON_ERR_PRINT_PRIV, \ .json = buf, \ .len = buf_len, \ .flags = rflags \ } /** @brief Reader flags. */ enum ujson_reader_flags { /** @brief If set warnings are treated as errors. */ UJSON_READER_STRICT = 0x01, }; /** * @brief A JSON parser internal state. */ struct ujson_reader { /** Pointer to a null terminated JSON string */ const char *json; /** A length of the JSON string */ size_t len; /** A current offset into the JSON string */ size_t off; /** An offset to the start of the last array or object */ size_t sub_off; /** Recursion depth increased when array/object is entered decreased on leave */ unsigned int depth; /** Maximal recursion depth */ unsigned int max_depth; /** Reader flags. */ enum ujson_reader_flags flags; /** Handler to print errors and warnings */ void (*err_print)(void *err_print_priv, const char *line); void *err_print_priv; char err[UJSON_ERR_MAX]; char buf[]; }; /** * @brief An ujson_val initializer. * * @param sbuf A pointer to a buffer used for string values. * @param sbuf_size A length of the buffer used for string values. * * @return An ujson_val initialized with default values. */ #define UJSON_VAL_INIT(sbuf, sbuf_size) { \ .buf = sbuf, \ .buf_size = sbuf_size, \ } /** * @brief A parsed JSON key value pair. */ struct ujson_val { /** * @brief A value type * * UJSON_VALUE_VOID means that no value was parsed. */ enum ujson_type type; /** An user supplied buffer and size to store a string values to. */ char *buf; size_t buf_size; /** * @brief An index to attribute list. * * This is set by the ujson_obj_first_filter() and * ujson_obj_next_filter() functions. */ size_t idx; /** An union to store the parsed value into. */ union { /** @brief A boolean value. */ int val_bool; /** @brief An integer value. */ long long val_int; /** @brief A string value. */ const char *val_str; }; /** * @brief A floating point value. * * Since integer values are subset of floating point values val_float * is always set when val_int was set. */ double val_float; /** @brief An ID for object values */ char id[UJSON_ID_MAX]; char buf__[]; }; /** * @brief Allocates a JSON value. * * @param buf_size A maximal buffer size for a string value, pass 0 for default. * @return A newly allocated JSON value. */ ujson_val *ujson_val_alloc(size_t buf_size); /** * @brief Frees a JSON value. * * @param self A JSON value previously allocated by ujson_val_alloc(). */ void ujson_val_free(ujson_val *self); /** * @brief Checks is result has valid type. * * @param res An ujson value. * @return Zero if result is not valid, non-zero otherwise. */ static inline int ujson_val_valid(struct ujson_val *res) { return !!res->type; } /** * @brief Fills the reader error. * * Once buffer error is set all parsing functions return immediatelly with type * set to UJSON_VOID. * * @param self An ujson_reader * @param fmt A printf like format string * @param ... A printf like parameters */ void ujson_err(ujson_reader *self, const char *fmt, ...) __attribute__((format(printf, 2, 3))); /** * @brief Prints error stored in the buffer. * * The error takes into consideration the current offset in the buffer and * prints a few preceding lines along with the exact position of the error. * * The error is passed to the err_print() handler. * * @param self A ujson_reader */ void ujson_err_print(ujson_reader *self); /** * @brief Prints a warning. * * Uses the print handler in the buffer to print a warning along with a few * lines of context from the JSON at the current position. * * @param self A ujson_reader * @param fmt A printf-like error string. * @param ... A printf-like parameters. */ void ujson_warn(ujson_reader *self, const char *fmt, ...) __attribute__((format(printf, 2, 3))); /** * @brief Returns true if error was encountered. * * @param self A ujson_reader * @return True if error was encountered false otherwise. */ static inline int ujson_reader_err(ujson_reader *self) { return !!self->err[0]; } /** * @brief Returns the type of next element in buffer. * * @param self An ujson_reader * @return A type of next element in the buffer. */ enum ujson_type ujson_next_type(ujson_reader *self); /** * @brief Returns if first element in JSON is object or array. * * @param self A ujson_reader * @return On success returns UJSON_OBJ or UJSON_ARR. On failure UJSON_VOID. */ enum ujson_type ujson_reader_start(ujson_reader *self); /** * @brief Starts parsing of a JSON object. * * @param self An ujson_reader * @param res An ujson_val to store the parsed value to. * * @return Zero on success, non-zero otherwise. */ int ujson_obj_first(ujson_reader *self, struct ujson_val *res); /** * @brief Parses next value from a JSON object. * * If the res->type is UJSON_OBJ or UJSON_ARR it has to be parsed or skipped * before next call to this function. * * @param self An ujson_reader. * @param res A ujson_val to store the parsed value to. * * @return Zero on success, non-zero otherwise. */ int ujson_obj_next(ujson_reader *self, struct ujson_val *res); /** * @brief A loop over a JSON object. * * @code * UJSON_OBJ_FOREACH(reader, val) { * printf("Got value id '%s' type '%s'", val->id, ujson_type_name(val->type)); * ... * } * @endcode * * @param self An ujson_reader. * @param res An ujson_val to store the next parsed value to. */ #define UJSON_OBJ_FOREACH(self, res) \ for (ujson_obj_first(self, res); ujson_val_valid(res); ujson_obj_next(self, res)) /** * @brief Utility function for log(n) lookup in a sorted array. * * @param list Analphabetically sorted array. * @param list_len Array length. * * @return An array index or (size_t)-1 if key wasn't found. */ size_t ujson_lookup(const void *arr, size_t memb_size, size_t list_len, const char *key); /** * @brief A JSON object attribute description i.e. key and type. */ typedef struct ujson_obj_attr { /** @brief A JSON object key name. */ const char *key; /** * @brief A JSON object value type. * * Note that because integer numbers are subset of floating point * numbers if requested type was UJSON_FLOAT it will match if parsed * type was UJSON_INT and the val_float will be set in addition to * val_int. */ enum ujson_type type; } ujson_obj_attr; /** @brief A JSON object description */ typedef struct ujson_obj { /** * @brief A list of attributes. * * Attributes we are looking for, the parser sets the val->idx for these. */ const ujson_obj_attr *attrs; /** @brief A size of attrs array. */ size_t attr_cnt; } ujson_obj; static inline size_t ujson_obj_lookup(const ujson_obj *obj, const char *key) { return ujson_lookup(obj->attrs, sizeof(*obj->attrs), obj->attr_cnt, key); } /** @brief An ujson_obj_attr initializer. */ #define UJSON_OBJ_ATTR(keyv, typev) \ {.key = keyv, .type = typev} /** @brief An ujson_obj_attr intializer with an array index. */ #define UJSON_OBJ_ATTR_IDX(key_idx, keyv, typev) \ [key_idx] = {.key = keyv, .type = typev} /** * @brief Starts parsing of a JSON object with attribute lists. * * @param self An ujson_reader. * @param res An ujson_val to store the parsed value to. * @param obj An ujson_obj object description. * @param ign A list of keys to ignore. * * @return Zero on success, non-zero otherwise. */ int ujson_obj_first_filter(ujson_reader *self, struct ujson_val *res, const struct ujson_obj *obj, const struct ujson_obj *ign); /** * @brief An empty object attribute list. * * To be passed to UJSON_OBJ_FOREACH_FITLER() as ignore list. */ extern const struct ujson_obj *ujson_empty_obj; /** * @brief Parses next value from a JSON object with attribute lists. * * If the res->type is UJSON_OBJ or UJSON_ARR it has to be parsed or skipped * before next call to this function. * * @param self An ujson_reader. * @param res An ujson_val to store the parsed value to. * @param obj An ujson_obj object description. * @param ign A list of keys to ignore. If set to NULL all unknown keys are * ignored, if set to ujson_empty_obj all unknown keys produce warnings. * * @return Zero on success, non-zero otherwise. */ int ujson_obj_next_filter(ujson_reader *self, struct ujson_val *res, const struct ujson_obj *obj, const struct ujson_obj *ign); /** * @brief A loop over a JSON object with a pre-defined list of expected attributes. * * @code * static struct ujson_obj_attr attrs[] = { * UJSON_OBJ_ATTR("bool", UJSON_BOOL), * UJSON_OBJ_ATTR("number", UJSON_INT), * }; * * static struct ujson_obj obj = { * .attrs = filter_attrs, * .attr_cnt = UJSON_ARRAY_SIZE(filter_attrs) * }; * * UJSON_OBJ_FOREACH_FILTER(reader, val, &obj, NULL) { * printf("Got value id '%s' type '%s'", * attrs[val->idx].id, ujson_type_name(val->type)); * ... * } * @endcode * * @param self An ujson_reader. * @param res An ujson_val to store the next parsed value to. * @param obj An ujson_obj with a description of attributes to parse. * @param ign An ujson_obj with a description of attributes to ignore. */ #define UJSON_OBJ_FOREACH_FILTER(self, res, obj, ign) \ for (ujson_obj_first_filter(self, res, obj, ign); \ ujson_val_valid(res); \ ujson_obj_next_filter(self, res, obj, ign)) /** * @brief Skips parsing of a JSON object. * * @param self An ujson_reader. * * @return Zero on success, non-zero otherwise. */ int ujson_obj_skip(ujson_reader *self); /** * @brief Starts parsing of a JSON array. * * @param self An ujson_reader. * @param res An ujson_val to store the parsed value to. * * @return Zero on success, non-zero otherwise. */ int ujson_arr_first(ujson_reader *self, struct ujson_val *res); /** * @brief Parses next value from a JSON array. * * If the res->type is UJSON_OBJ or UJSON_ARR it has to be parsed or skipped * before next call to this function. * * @param self An ujson_reader. * @param res An ujson_val to store the parsed value to. * * @return Zero on success, non-zero otherwise. */ int ujson_arr_next(ujson_reader *self, struct ujson_val *res); /** * @brief A loop over a JSON array. * * @code * UJSON_ARR_FOREACH(reader, val) { * printf("Got value type '%s'", ujson_type_name(val->type)); * ... * } * @endcode * * @param self An ujson_reader. * @param res An ujson_val to store the next parsed value to. */ #define UJSON_ARR_FOREACH(self, res) \ for (ujson_arr_first(self, res); ujson_val_valid(res); ujson_arr_next(self, res)) /** * @brief Skips parsing of a JSON array. * * @param self A ujson_reader. * * @return Zero on success, non-zero otherwise. */ int ujson_arr_skip(ujson_reader *self); /** * @brief A JSON reader state. */ typedef struct ujson_reader_state { size_t off; unsigned int depth; } ujson_reader_state; /** * @brief Returns a parser state at the start of current object/array. * * This function could be used for the parser to return to the start of the * currently parsed object or array. * * @param self A ujson_reader * @return A state that points to a start of the last object or array. */ static inline ujson_reader_state ujson_reader_state_save(ujson_reader *self) { struct ujson_reader_state ret = { .off = self->sub_off, .depth = self->depth, }; return ret; } /** * @brief Returns the parser to a saved state. * * This function could be used for the parser to return to the start of * object or array saved by t the ujson_reader_state_get() function. * * @param self A ujson_reader * @param state An parser state as returned by the ujson_reader_state_get(). */ static inline void ujson_reader_state_load(ujson_reader *self, ujson_reader_state state) { if (ujson_reader_err(self)) return; self->off = state.off; self->sub_off = state.off; self->depth = state.depth; } /** * @brief Resets the parser to a start. * * @param self A ujson_reader */ static inline void ujson_reader_reset(ujson_reader *self) { self->off = 0; self->sub_off = 0; self->depth = 0; self->err[0] = 0; } /** * @brief Loads a file into an ujson_reader buffer. * * The reader has to be later freed by ujson_reader_free(). * * @param path A path to a file. * @return A ujson_reader or NULL in a case of a failure. */ ujson_reader *ujson_reader_load(const char *path); /** * @brief Frees an ujson_reader buffer. * * @param self A ujson_reader allocated by ujson_reader_load() function. */ void ujson_reader_free(ujson_reader *self); /** * @brief Prints errors and warnings at the end of parsing. * * Checks if self->err is set and prints the error with ujson_reader_err() * * Checks if there is any text left after the parser has finished with * ujson_reader_consumed() and prints a warning if there were any non-whitespace * characters left. * * @param self A ujson_reader */ void ujson_reader_finish(ujson_reader *self); /** * @brief Returns non-zero if whole buffer has been consumed. * * @param self A ujson_reader. * @return Non-zero if whole buffer was consumed. */ static inline int ujson_reader_consumed(ujson_reader *self) { return self->off >= self->len; } #endif /* UJSON_H */