|
|
@@ -0,0 +1,773 @@
|
|
|
+/**
|
|
|
+ * \file lwjson.c
|
|
|
+ * \brief 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
|
|
|
+ */
|
|
|
+#include <string.h>
|
|
|
+#include "lwjson.h"
|
|
|
+
|
|
|
+/**
|
|
|
+ * \brief Internal string object
|
|
|
+ */
|
|
|
+typedef struct {
|
|
|
+ const char* start; /*!< Original pointer to beginning of JSON object */
|
|
|
+ size_t len; /*!< Total length of input json string */
|
|
|
+ const char* p; /*!< Current char pointer */
|
|
|
+} lwjson_int_str_t;
|
|
|
+
|
|
|
+/**
|
|
|
+ * \brief Allocate new token for JSON block
|
|
|
+ * \param[in] lwobj: LwJSON instance
|
|
|
+ * \return Pointer to new token
|
|
|
+ */
|
|
|
+static lwjson_token_t*
|
|
|
+prv_alloc_token(lwjson_t* lwobj) {
|
|
|
+ if (lwobj->next_free_token_pos < lwobj->tokens_len) {
|
|
|
+ LWJSON_MEMSET(&lwobj->tokens[lwobj->next_free_token_pos], 0x00, sizeof(*lwobj->tokens));
|
|
|
+ return &lwobj->tokens[lwobj->next_free_token_pos++];
|
|
|
+ }
|
|
|
+ return NULL;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * \brief Skip all characters that are considered *blank* as per RFC4627
|
|
|
+ * \param[in,out] pobj: Pointer to text that is modified on success
|
|
|
+ * \return \ref lwjsonOK on success, member of \ref lwjsonr_t otherwise
|
|
|
+ */
|
|
|
+static lwjsonr_t
|
|
|
+prv_skip_blank(lwjson_int_str_t* pobj) {
|
|
|
+ while (pobj->p != NULL && (size_t)(pobj->p - pobj->start) < pobj->len && *pobj->p != '\0') {
|
|
|
+ if (*pobj->p == ' ' || *pobj->p == '\t' || *pobj->p == '\r' || *pobj->p == '\n' || *pobj->p == '\f') {
|
|
|
+ ++pobj->p;
|
|
|
+#if LWJSON_CFG_COMMENTS
|
|
|
+ /* Check for comments and remove them */
|
|
|
+ } else if (*pobj->p == '/') {
|
|
|
+ ++pobj->p;
|
|
|
+ if (pobj->p != NULL && (size_t)(pobj->p - pobj->start) < pobj->len && *pobj->p == '*') {
|
|
|
+ ++pobj->p;
|
|
|
+ while (pobj->p != NULL && (size_t)(pobj->p - pobj->start) < pobj->len && *pobj->p != '\0') {
|
|
|
+ if (*pobj->p == '*') {
|
|
|
+ ++pobj->p;
|
|
|
+ if ((size_t)(pobj->p - pobj->start) < pobj->len && *pobj->p == '/') {
|
|
|
+ ++pobj->p;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ++pobj->p;
|
|
|
+ }
|
|
|
+ }
|
|
|
+#endif /* LWJSON_CFG_COMMENTS */
|
|
|
+ } else {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (pobj->p != NULL && (size_t)(pobj->p - pobj->start) < pobj->len && *pobj->p != '\0') {
|
|
|
+ return lwjsonOK;
|
|
|
+ }
|
|
|
+ return lwjsonERRJSON;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * \brief Parse JSON string that must start end end with double quotes `"` character
|
|
|
+ * It just parses length of characters and does not perform any decode operation
|
|
|
+ * \param[in,out] pobj: Pointer to text that is modified on success
|
|
|
+ * \param[out] pout: Pointer to pointer to string that is set where string starts
|
|
|
+ * \param[out] poutlen: Length of string in units of characters is stored here
|
|
|
+ * \return \ref lwjsonOK on success, member of \ref lwjsonr_t otherwise
|
|
|
+ */
|
|
|
+static lwjsonr_t
|
|
|
+prv_parse_string(lwjson_int_str_t* pobj, const char** pout, size_t* poutlen) {
|
|
|
+ lwjsonr_t res;
|
|
|
+ size_t len = 0;
|
|
|
+
|
|
|
+ res = prv_skip_blank(pobj);
|
|
|
+ if (res != lwjsonOK) {
|
|
|
+ return res;
|
|
|
+ }
|
|
|
+ if ((size_t)(pobj->p - pobj->start) < pobj->len && *pobj->p++ != '"') {
|
|
|
+ return lwjsonERRJSON;
|
|
|
+ }
|
|
|
+ *pout = pobj->p;
|
|
|
+ /* Parse string but take care of escape characters */
|
|
|
+ for (;; ++pobj->p, ++len) {
|
|
|
+ if (pobj->p == NULL || (size_t)(pobj->p - pobj->start) >= pobj->len || *pobj->p == '\0') {
|
|
|
+ return lwjsonERRJSON;
|
|
|
+ }
|
|
|
+ /* Check special characters */
|
|
|
+ if (*pobj->p == '\\') {
|
|
|
+ ++pobj->p;
|
|
|
+ ++len;
|
|
|
+
|
|
|
+ if ((size_t)(pobj->p - pobj->start) >= pobj->len) {
|
|
|
+ return lwjsonERRJSON;
|
|
|
+ }
|
|
|
+
|
|
|
+ switch (*pobj->p) {
|
|
|
+ case '"': /* fallthrough */
|
|
|
+ case '\\': /* fallthrough */
|
|
|
+ case '/': /* fallthrough */
|
|
|
+ case 'b': /* fallthrough */
|
|
|
+ case 'f': /* fallthrough */
|
|
|
+ case 'n': /* fallthrough */
|
|
|
+ case 'r': /* fallthrough */
|
|
|
+ case 't': break;
|
|
|
+ case 'u':
|
|
|
+ ++pobj->p;
|
|
|
+ for (size_t i = 0; i < 4; ++i, ++len) {
|
|
|
+ if (!(((size_t)(pobj->p - pobj->start) < pobj->len)
|
|
|
+ && ((*pobj->p >= '0' && *pobj->p <= '9') || (*pobj->p >= 'a' && *pobj->p <= 'f')
|
|
|
+ || (*pobj->p >= 'A' && *pobj->p <= 'F')))) {
|
|
|
+ return lwjsonERRJSON;
|
|
|
+ }
|
|
|
+ if (i < 3) {
|
|
|
+ ++pobj->p;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ default: return lwjsonERRJSON;
|
|
|
+ }
|
|
|
+ } else if (*pobj->p == '"') {
|
|
|
+ ++pobj->p;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ *poutlen = len;
|
|
|
+ return res;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * \brief Parse property name that must comply with JSON string format as in RFC4627
|
|
|
+ * Property string must be followed by colon character ":"
|
|
|
+ * \param[in,out] pobj: Pointer to text that is modified on success
|
|
|
+ * \param[out] t: Token instance to write property name to
|
|
|
+ * \return \ref lwjsonOK on success, member of \ref lwjsonr_t otherwise
|
|
|
+ */
|
|
|
+static lwjsonr_t
|
|
|
+prv_parse_property_name(lwjson_int_str_t* pobj, lwjson_token_t* t) {
|
|
|
+ lwjsonr_t res;
|
|
|
+
|
|
|
+ /* Parse property string first */
|
|
|
+ res = prv_parse_string(pobj, &t->token_name, &t->token_name_len);
|
|
|
+ if (res != lwjsonOK) {
|
|
|
+ return res;
|
|
|
+ }
|
|
|
+ /* Skip any spaces */
|
|
|
+ res = prv_skip_blank(pobj);
|
|
|
+ if (res != lwjsonOK) {
|
|
|
+ return res;
|
|
|
+ }
|
|
|
+ /* Must continue with colon */
|
|
|
+ if ((size_t)(pobj->p - pobj->start) >= pobj->len || *pobj->p++ != ':') {
|
|
|
+ return lwjsonERRJSON;
|
|
|
+ }
|
|
|
+ /* Skip any spaces */
|
|
|
+ res = prv_skip_blank(pobj);
|
|
|
+ if (res != lwjsonOK) {
|
|
|
+ return res;
|
|
|
+ }
|
|
|
+ return lwjsonOK;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * \brief Calculate new value for exponent 10^exponent.
|
|
|
+ * \param[in] exp: Exponent.
|
|
|
+ * \param[in] is_minus: Indicates whether the exponent is negative.
|
|
|
+ * \return Value of 10^exponent.
|
|
|
+ */
|
|
|
+static lwjson_real_t
|
|
|
+prv_power10(lwjson_int_t exp, uint8_t is_minus) {
|
|
|
+ lwjson_real_t result = (lwjson_real_t)1;
|
|
|
+ lwjson_real_t base = (lwjson_real_t)10;
|
|
|
+
|
|
|
+ for (; exp > 0; base *= base, exp >>= 1) {
|
|
|
+ if ((exp & 1) == 1) {
|
|
|
+ result *= base;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return is_minus ? ((lwjson_real_t)1 / result) : result;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * \brief Parse number as described in RFC4627
|
|
|
+ * \param[in,out] pobj: Pointer to text that is modified on success
|
|
|
+ * \param[out] tout: Pointer to output number format
|
|
|
+ * \param[out] fout: Pointer to output real-type variable. Used if type is REAL.
|
|
|
+ * \param[out] iout: Pointer to output int-type variable. Used if type is INT.
|
|
|
+ * \return \ref lwjsonOK on success, member of \ref lwjsonr_t otherwise
|
|
|
+ */
|
|
|
+static lwjsonr_t
|
|
|
+prv_parse_number(lwjson_int_str_t* pobj, lwjson_type_t* tout, lwjson_real_t* fout, lwjson_int_t* iout) {
|
|
|
+ lwjsonr_t res;
|
|
|
+ uint8_t is_minus;
|
|
|
+ lwjson_real_t real_num = 0;
|
|
|
+ lwjson_int_t int_num = 0;
|
|
|
+ lwjson_type_t type = LWJSON_TYPE_NUM_INT;
|
|
|
+
|
|
|
+ res = prv_skip_blank(pobj);
|
|
|
+ if (res != lwjsonOK) {
|
|
|
+ return res;
|
|
|
+ }
|
|
|
+ if ((size_t)(pobj->p - pobj->start) >= pobj->len || *pobj->p == '\0') {
|
|
|
+ return lwjsonERRJSON;
|
|
|
+ }
|
|
|
+ is_minus = *pobj->p == '-' ? (++pobj->p, 1) : 0;
|
|
|
+ if ((size_t)(pobj->p - pobj->start) >= pobj->len /* End of json input */
|
|
|
+ || *pobj->p == '\0' /* Invalid string */
|
|
|
+ || *pobj->p < '0' || *pobj->p > '9' /* Character outside number range */
|
|
|
+ || (*pobj->p == '0' && (size_t)(pobj->p + 1 - pobj->start) < pobj->len
|
|
|
+ && (pobj->p[1] >= '0' && pobj->p[1] <= '9'))) { /* Number starts with 0 but not followed by dot */
|
|
|
+ return lwjsonERRJSON;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Parse number */
|
|
|
+ for (int_num = 0; (size_t)(pobj->p - pobj->start) < pobj->len && *pobj->p >= '0' && *pobj->p <= '9'; ++pobj->p) {
|
|
|
+ int_num = int_num * (lwjson_int_t)10 + (*pobj->p - '0');
|
|
|
+ }
|
|
|
+
|
|
|
+ real_num = (lwjson_real_t)int_num;
|
|
|
+
|
|
|
+ if (pobj->p != NULL && (size_t)(pobj->p - pobj->start) < pobj->len && *pobj->p == '.') { /* Number has exponent */
|
|
|
+ lwjson_real_t exp;
|
|
|
+ lwjson_int_t dec_num;
|
|
|
+
|
|
|
+ real_num = (lwjson_real_t)int_num;
|
|
|
+
|
|
|
+ type = LWJSON_TYPE_NUM_REAL; /* Format is real */
|
|
|
+ ++pobj->p; /* Ignore comma character */
|
|
|
+ if ((size_t)(pobj->p - pobj->start) >= pobj->len || *pobj->p < '0'
|
|
|
+ || *pobj->p > '9') { /* Must be followed by number characters */
|
|
|
+ return lwjsonERRJSON;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Get number after decimal point */
|
|
|
+ for (exp = (lwjson_real_t)1, dec_num = 0;
|
|
|
+ (size_t)(pobj->p - pobj->start) < pobj->len && *pobj->p >= '0' && *pobj->p <= '9';
|
|
|
+ ++pobj->p, exp *= (lwjson_real_t)10) {
|
|
|
+ dec_num = dec_num * (lwjson_int_t)10 + (lwjson_int_t)(*pobj->p - '0');
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Add decimal part to number */
|
|
|
+ real_num += (lwjson_real_t)dec_num / exp;
|
|
|
+ }
|
|
|
+ if (pobj->p != NULL && (size_t)(pobj->p - pobj->start) < pobj->len
|
|
|
+ && (*pobj->p == 'e' || *pobj->p == 'E')) { /* Engineering mode */
|
|
|
+ uint8_t is_minus_exp;
|
|
|
+ lwjson_int_t exp_cnt;
|
|
|
+
|
|
|
+ type = LWJSON_TYPE_NUM_REAL; /* Format is real */
|
|
|
+ ++pobj->p; /* Ignore enginnering sing part */
|
|
|
+ if ((size_t)(pobj->p - pobj->start) >= pobj->len) {
|
|
|
+ return lwjsonERRJSON;
|
|
|
+ }
|
|
|
+ is_minus_exp = *pobj->p == '-' ? (++pobj->p, 1) : 0; /* Check if negative */
|
|
|
+ if ((size_t)(pobj->p - pobj->start) < pobj->len && *pobj->p == '+') { /* Optional '+' is possible too */
|
|
|
+ ++pobj->p;
|
|
|
+ }
|
|
|
+ if ((size_t)(pobj->p - pobj->start) >= pobj->len || *pobj->p < '0'
|
|
|
+ || *pobj->p > '9') { /* Must be followed by number characters */
|
|
|
+ return lwjsonERRJSON;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Parse exponent number */
|
|
|
+ for (exp_cnt = 0; (size_t)(pobj->p - pobj->start) < pobj->len && *pobj->p >= '0' && *pobj->p <= '9';
|
|
|
+ ++pobj->p) {
|
|
|
+ exp_cnt = exp_cnt * (lwjson_int_t)10 + (lwjson_int_t)(*pobj->p - '0');
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Calculate new value for exponent 10^exponent */
|
|
|
+ real_num *= prv_power10(exp_cnt, is_minus_exp);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Write output values */
|
|
|
+ if (tout != NULL) {
|
|
|
+ *tout = type;
|
|
|
+ }
|
|
|
+ if (type == LWJSON_TYPE_NUM_INT) {
|
|
|
+ *iout = is_minus ? -int_num : int_num;
|
|
|
+ } else {
|
|
|
+ *fout = is_minus ? -real_num : real_num;
|
|
|
+ }
|
|
|
+ return lwjsonOK;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * \brief Create path segment from input path for search operation
|
|
|
+ * \param[in,out] ppath: Pointer to pointer to input path. Pointer is modified
|
|
|
+ * \param[out] opath: Pointer to pointer to write path segment
|
|
|
+ * \param[out] olen: Pointer to variable to write length of segment
|
|
|
+ * \param[out] is_last: Pointer to write if this is last segment
|
|
|
+ * \return `1` on success, `0` otherwise
|
|
|
+ */
|
|
|
+static uint8_t
|
|
|
+prv_create_path_segment(const char** ppath, const char** opath, size_t* olen, uint8_t* is_last) {
|
|
|
+ const char* segment = *ppath;
|
|
|
+
|
|
|
+ *is_last = 0;
|
|
|
+ *opath = NULL;
|
|
|
+ *olen = 0;
|
|
|
+
|
|
|
+ /* Check input path */
|
|
|
+ if (segment == NULL || *segment == '\0') {
|
|
|
+ *is_last = 1;
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Path must be one of:
|
|
|
+ * - literal text
|
|
|
+ * - "#" followed by dot "."
|
|
|
+ */
|
|
|
+ if (*segment == '#') {
|
|
|
+ *opath = segment;
|
|
|
+ for (*olen = 0;; ++segment, ++(*olen)) {
|
|
|
+ if (*segment == '.') {
|
|
|
+ ++segment;
|
|
|
+ break;
|
|
|
+ } else if (*segment == '\0') {
|
|
|
+ if (*olen == 1) {
|
|
|
+ return 0;
|
|
|
+ } else {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ *ppath = segment;
|
|
|
+ } else {
|
|
|
+ *opath = segment;
|
|
|
+ for (*olen = 0; *segment != '\0' && *segment != '.'; ++(*olen), ++segment) {}
|
|
|
+ *ppath = segment + 1;
|
|
|
+ }
|
|
|
+ if (*segment == '\0') {
|
|
|
+ *is_last = 1;
|
|
|
+ }
|
|
|
+ return 1;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * \brief Input recursive function for find operation
|
|
|
+ * \param[in] parent: Parent token of type \ref LWJSON_TYPE_ARRAY or LWJSON_TYPE_OBJECT
|
|
|
+ * \param[in] path: Path to search for starting this token further
|
|
|
+ * \return Found token on success, `NULL` otherwise
|
|
|
+ */
|
|
|
+static const lwjson_token_t*
|
|
|
+prv_find(const lwjson_token_t* parent, const char* path) {
|
|
|
+ const char* segment;
|
|
|
+ size_t segment_len;
|
|
|
+ uint8_t is_last, res;
|
|
|
+
|
|
|
+ /* Get path segments */
|
|
|
+ res = prv_create_path_segment(&path, &segment, &segment_len, &is_last);
|
|
|
+ if (res != 0) {
|
|
|
+ /* Check if detected an array request */
|
|
|
+ if (*segment == '#') {
|
|
|
+ /* Parent must be array */
|
|
|
+ if (parent->type != LWJSON_TYPE_ARRAY) {
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Check if index requested */
|
|
|
+ if (segment_len > 1) {
|
|
|
+ const lwjson_token_t* tkn;
|
|
|
+ size_t index = 0;
|
|
|
+
|
|
|
+ /* Parse number */
|
|
|
+ for (size_t i = 1; i < segment_len; ++i) {
|
|
|
+ if (segment[i] < '0' || segment[i] > '9') {
|
|
|
+ return NULL;
|
|
|
+ } else {
|
|
|
+ index = index * 10 + (segment[i] - '0');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Start from beginning */
|
|
|
+ for (tkn = parent->u.first_child; tkn != NULL && index > 0; tkn = tkn->next, --index) {}
|
|
|
+ if (tkn != NULL) {
|
|
|
+ if (is_last) {
|
|
|
+ return tkn;
|
|
|
+ } else {
|
|
|
+ return prv_find(tkn, path);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Scan all indexes and get first match */
|
|
|
+ for (const lwjson_token_t* tkn = parent->u.first_child; tkn != NULL; tkn = tkn->next) {
|
|
|
+ const lwjson_token_t* tmp = prv_find(tkn, path);
|
|
|
+ if (tmp != NULL) {
|
|
|
+ return tmp;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if (parent->type != LWJSON_TYPE_OBJECT) {
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+ for (const lwjson_token_t* tkn = parent->u.first_child; tkn != NULL; tkn = tkn->next) {
|
|
|
+ if (tkn->token_name_len == segment_len && !strncmp(tkn->token_name, segment, segment_len)) {
|
|
|
+ const lwjson_token_t* tmp;
|
|
|
+ if (is_last) {
|
|
|
+ return tkn;
|
|
|
+ }
|
|
|
+ tmp = prv_find(tkn, path);
|
|
|
+ if (tmp != NULL) {
|
|
|
+ return tmp;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return NULL;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * \brief Check for character after opening bracket of array or object
|
|
|
+ * \param[in,out] pobj: JSON string
|
|
|
+ * \param[in] t: Token to check for type
|
|
|
+ * \return \ref lwjsonOK on success, member of \ref lwjsonr_t otherwise
|
|
|
+ */
|
|
|
+static inline lwjsonr_t
|
|
|
+prv_check_valid_char_after_open_bracket(lwjson_int_str_t* pobj, lwjson_token_t* t) {
|
|
|
+ lwjsonr_t res;
|
|
|
+
|
|
|
+ /* Check next character after object open */
|
|
|
+ res = prv_skip_blank(pobj);
|
|
|
+ if (res != lwjsonOK) {
|
|
|
+ return res;
|
|
|
+ }
|
|
|
+ if ((size_t)(pobj->p - pobj->start) >= pobj->len || *pobj->p == '\0'
|
|
|
+ || (t->type == LWJSON_TYPE_OBJECT && (*pobj->p != '"' && *pobj->p != '}'))
|
|
|
+ || (t->type == LWJSON_TYPE_ARRAY
|
|
|
+ && (*pobj->p != '"' && *pobj->p != ']' && *pobj->p != '[' && *pobj->p != '{' && *pobj->p != '-'
|
|
|
+ && (*pobj->p < '0' || *pobj->p > '9') && *pobj->p != 't' && *pobj->p != 'n' && *pobj->p != 'f'))) {
|
|
|
+ res = lwjsonERRJSON;
|
|
|
+ }
|
|
|
+ return res;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * \brief Setup LwJSON instance for parsing JSON strings
|
|
|
+ * \param[in,out] lwobj: LwJSON instance
|
|
|
+ * \param[in] tokens: Pointer to array of tokens used for parsing
|
|
|
+ * \param[in] tokens_len: Number of tokens
|
|
|
+ * \return \ref lwjsonOK on success, member of \ref lwjsonr_t otherwise
|
|
|
+ */
|
|
|
+lwjsonr_t
|
|
|
+lwjson_init(lwjson_t* lwobj, lwjson_token_t* tokens, size_t tokens_len) {
|
|
|
+ LWJSON_MEMSET(lwobj, 0x00, sizeof(*lwobj));
|
|
|
+ LWJSON_MEMSET(tokens, 0x00, sizeof(*tokens) * tokens_len);
|
|
|
+ lwobj->tokens = tokens;
|
|
|
+ lwobj->tokens_len = tokens_len;
|
|
|
+ lwobj->first_token.type = LWJSON_TYPE_OBJECT;
|
|
|
+ return lwjsonOK;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * \brief Parse JSON data with length parameter
|
|
|
+ * JSON format must be complete and must comply with RFC4627
|
|
|
+ * \param[in,out] lwobj: LwJSON instance
|
|
|
+ * \param[in] json_data: JSON string to parse
|
|
|
+ * \param[in] jsonČlen: JSON data length
|
|
|
+ * \return \ref lwjsonOK on success, member of \ref lwjsonr_t otherwise
|
|
|
+ */
|
|
|
+lwjsonr_t
|
|
|
+lwjson_parse_ex(lwjson_t* lwobj, const void* json_data, size_t json_len) {
|
|
|
+ lwjsonr_t res = lwjsonOK;
|
|
|
+ lwjson_token_t *t, *to;
|
|
|
+ lwjson_int_str_t pobj = {.start = json_data, .len = json_len, .p = json_data};
|
|
|
+
|
|
|
+ /* Check input parameters */
|
|
|
+ if (lwobj == NULL || json_data == NULL || json_len == 0) {
|
|
|
+ res = lwjsonERRPAR;
|
|
|
+ goto ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* set first token */
|
|
|
+ to = &lwobj->first_token;
|
|
|
+
|
|
|
+ /* values from very beginning */
|
|
|
+ lwobj->flags.parsed = 0;
|
|
|
+ lwobj->next_free_token_pos = 0;
|
|
|
+ LWJSON_MEMSET(to, 0x00, sizeof(*to));
|
|
|
+
|
|
|
+ /* First parse */
|
|
|
+ res = prv_skip_blank(&pobj);
|
|
|
+ if (res != lwjsonOK) {
|
|
|
+ goto ret;
|
|
|
+ }
|
|
|
+ if ((size_t)(pobj.p - pobj.start) >= pobj.len) {
|
|
|
+ res = lwjsonERRJSON;
|
|
|
+ goto ret;
|
|
|
+ }
|
|
|
+ if (*pobj.p == '{') {
|
|
|
+ to->type = LWJSON_TYPE_OBJECT;
|
|
|
+ } else if (*pobj.p == '[') {
|
|
|
+ to->type = LWJSON_TYPE_ARRAY;
|
|
|
+ } else {
|
|
|
+ res = lwjsonERRJSON;
|
|
|
+ goto ret;
|
|
|
+ }
|
|
|
+ ++pobj.p;
|
|
|
+ res = prv_check_valid_char_after_open_bracket(&pobj, to);
|
|
|
+ if (res != lwjsonOK) {
|
|
|
+ goto ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Process all characters as indicated by input user */
|
|
|
+ while (pobj.p != NULL && (size_t)(pobj.p - pobj.start) < pobj.len && *pobj.p != '\0') {
|
|
|
+ /* Filter out blanks */
|
|
|
+ res = prv_skip_blank(&pobj);
|
|
|
+ if (res != lwjsonOK) {
|
|
|
+ goto ret;
|
|
|
+ }
|
|
|
+ if (*pobj.p == ',') {
|
|
|
+ ++pobj.p;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Check if end of object or array*/
|
|
|
+ if (*pobj.p == (to->type == LWJSON_TYPE_OBJECT ? '}' : ']')) {
|
|
|
+ lwjson_token_t* parent = to->next;
|
|
|
+ to->next = NULL;
|
|
|
+ ++pobj.p;
|
|
|
+
|
|
|
+ /* End of string if to == NULL (no parent), check if properly terminated */
|
|
|
+ to = parent;
|
|
|
+ if (to == NULL) {
|
|
|
+ prv_skip_blank(&pobj);
|
|
|
+ res = (pobj.p == NULL || (size_t)(pobj.p - pobj.start) >= pobj.len || *pobj.p == '\0') ? lwjsonOK
|
|
|
+ : lwjsonERR;
|
|
|
+ goto ret;
|
|
|
+ }
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Allocate new token */
|
|
|
+ t = prv_alloc_token(lwobj);
|
|
|
+ if (t == NULL) {
|
|
|
+ res = lwjsonERRMEM;
|
|
|
+ goto ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* If object type is not array, first thing is property that starts with quotes */
|
|
|
+ if (to->type != LWJSON_TYPE_ARRAY) {
|
|
|
+ if (*pobj.p != '"') {
|
|
|
+ res = lwjsonERRJSON;
|
|
|
+ goto ret;
|
|
|
+ }
|
|
|
+ res = prv_parse_property_name(&pobj, t);
|
|
|
+ if (res != lwjsonOK) {
|
|
|
+ goto ret;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Add element to linked list */
|
|
|
+ if (to->u.first_child == NULL) {
|
|
|
+ to->u.first_child = t;
|
|
|
+ } else {
|
|
|
+ lwjson_token_t* c;
|
|
|
+ for (c = to->u.first_child; c->next != NULL; c = c->next) {}
|
|
|
+ c->next = t;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Check next character to process */
|
|
|
+ switch (*pobj.p) {
|
|
|
+ case '{':
|
|
|
+ case '[':
|
|
|
+ t->type = *pobj.p == '{' ? LWJSON_TYPE_OBJECT : LWJSON_TYPE_ARRAY;
|
|
|
+ ++pobj.p;
|
|
|
+
|
|
|
+ res = prv_check_valid_char_after_open_bracket(&pobj, t);
|
|
|
+ if (res != lwjsonOK) {
|
|
|
+ goto ret;
|
|
|
+ }
|
|
|
+ t->next = to; /* Temporary saved as parent object */
|
|
|
+ to = t;
|
|
|
+ break;
|
|
|
+ case '"':
|
|
|
+ res = prv_parse_string(&pobj, &t->u.str.token_value, &t->u.str.token_value_len);
|
|
|
+ if (res == lwjsonOK) {
|
|
|
+ t->type = LWJSON_TYPE_STRING;
|
|
|
+ } else {
|
|
|
+ goto ret;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case 't':
|
|
|
+ /* RFC4627 is lower-case only */
|
|
|
+ if ((size_t)(pobj.p + 3 - pobj.start) < pobj.len && strncmp(pobj.p, "true", 4) == 0) {
|
|
|
+ t->type = LWJSON_TYPE_TRUE;
|
|
|
+ pobj.p += 4;
|
|
|
+ } else {
|
|
|
+ res = lwjsonERRJSON;
|
|
|
+ goto ret;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case 'f':
|
|
|
+ /* RFC4627 is lower-case only */
|
|
|
+ if ((size_t)(pobj.p + 4 - pobj.start) < pobj.len && strncmp(pobj.p, "false", 5) == 0) {
|
|
|
+ t->type = LWJSON_TYPE_FALSE;
|
|
|
+ pobj.p += 5;
|
|
|
+ } else {
|
|
|
+ res = lwjsonERRJSON;
|
|
|
+ goto ret;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case 'n':
|
|
|
+ /* RFC4627 is lower-case only */
|
|
|
+ if ((size_t)(pobj.p + 3 - pobj.start) < pobj.len && strncmp(pobj.p, "null", 4) == 0) {
|
|
|
+ t->type = LWJSON_TYPE_NULL;
|
|
|
+ pobj.p += 4;
|
|
|
+ } else {
|
|
|
+ res = lwjsonERRJSON;
|
|
|
+ goto ret;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ if (*pobj.p == '-' || (*pobj.p >= '0' && *pobj.p <= '9')) {
|
|
|
+ if (prv_parse_number(&pobj, &t->type, &t->u.num_real, &t->u.num_int) != lwjsonOK) {
|
|
|
+ res = lwjsonERRJSON;
|
|
|
+ goto ret;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ res = lwjsonERRJSON;
|
|
|
+ goto ret;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Below code is used to check characters after valid tokens */
|
|
|
+ if (t->type == LWJSON_TYPE_ARRAY || t->type == LWJSON_TYPE_OBJECT) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Check what are values after the token value
|
|
|
+ *
|
|
|
+ * As per RFC4627, every token value may have one or more
|
|
|
+ * blank characters, followed by one of below options:
|
|
|
+ * - Comma separator for next token
|
|
|
+ * - End of array indication
|
|
|
+ * - End of object indication
|
|
|
+ */
|
|
|
+ res = prv_skip_blank(&pobj);
|
|
|
+ if (res != lwjsonOK) {
|
|
|
+ goto ret;
|
|
|
+ }
|
|
|
+ /* Check if valid string is availabe after */
|
|
|
+ if (pobj.p == NULL || (size_t)(pobj.p - pobj.start) >= pobj.len || *pobj.p == '\0'
|
|
|
+ || (*pobj.p != ',' && *pobj.p != ']' && *pobj.p != '}')) {
|
|
|
+ res = lwjsonERRJSON;
|
|
|
+ goto ret;
|
|
|
+ } else if (*pobj.p == ',') { /* Check to advance to next token immediately */
|
|
|
+ ++pobj.p;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (to != &lwobj->first_token || (to != NULL && to->next != NULL)) {
|
|
|
+ res = lwjsonERRJSON;
|
|
|
+ to = NULL;
|
|
|
+ }
|
|
|
+ if (to != NULL) {
|
|
|
+ if (to->type != LWJSON_TYPE_ARRAY && to->type != LWJSON_TYPE_OBJECT) {
|
|
|
+ res = lwjsonERRJSON;
|
|
|
+ }
|
|
|
+ to->token_name = NULL;
|
|
|
+ to->token_name_len = 0;
|
|
|
+ }
|
|
|
+ret:
|
|
|
+ if (res == lwjsonOK) {
|
|
|
+ lwobj->flags.parsed = 1;
|
|
|
+ }
|
|
|
+ return res;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * \brief Parse input JSON format
|
|
|
+ * JSON format must be complete and must comply with RFC4627
|
|
|
+ * \param[in,out] lwobj: LwJSON instance
|
|
|
+ * \param[in] json_str: JSON string to parse
|
|
|
+ * \return \ref lwjsonOK on success, member of \ref lwjsonr_t otherwise
|
|
|
+ */
|
|
|
+lwjsonr_t
|
|
|
+lwjson_parse(lwjson_t* lwobj, const char* json_str) {
|
|
|
+ return lwjson_parse_ex(lwobj, json_str, strlen(json_str));
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * \brief Free token instances (specially used in case of dynamic memory allocation)
|
|
|
+ * \param[in,out] lwobj: LwJSON instance
|
|
|
+ * \return \ref lwjsonOK on success, member of \ref lwjsonr_t otherwise
|
|
|
+ */
|
|
|
+lwjsonr_t
|
|
|
+lwjson_free(lwjson_t* lwobj) {
|
|
|
+ LWJSON_MEMSET(lwobj->tokens, 0x00, sizeof(*lwobj->tokens) * lwobj->tokens_len);
|
|
|
+ lwobj->flags.parsed = 0;
|
|
|
+ return lwjsonOK;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * \brief Find first match in the given path for JSON entry
|
|
|
+ * JSON must be valid and parsed with \ref lwjson_parse function
|
|
|
+ * \param[in] lwobj: JSON instance with parsed JSON string
|
|
|
+ * \param[in] path: Path with dot-separated entries to search for the JSON key to return
|
|
|
+ * \return Pointer to found token on success, `NULL` if token cannot be found
|
|
|
+ */
|
|
|
+const lwjson_token_t*
|
|
|
+lwjson_find(lwjson_t* lwobj, const char* path) {
|
|
|
+ if (lwobj == NULL || !lwobj->flags.parsed || path == NULL) {
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+ return prv_find(lwjson_get_first_token(lwobj), path);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * \brief Find first match in the given path for JSON path
|
|
|
+ * JSON must be valid and parsed with \ref lwjson_parse function
|
|
|
+ *
|
|
|
+ * \param[in] lwobj: JSON instance with parsed JSON string
|
|
|
+ * \param[in] token: Root token to start search at.
|
|
|
+ * Token must be type \ref LWJSON_TYPE_OBJECT or \ref LWJSON_TYPE_ARRAY.
|
|
|
+ * Set to `NULL` to use root token of LwJSON object
|
|
|
+ * \param[in] path: path with dot-separated entries to search for JSON key
|
|
|
+ * \return Pointer to found token on success, `NULL` if token cannot be found
|
|
|
+ */
|
|
|
+const lwjson_token_t*
|
|
|
+lwjson_find_ex(lwjson_t* lwobj, const lwjson_token_t* token, const char* path) {
|
|
|
+ if (lwobj == NULL || !lwobj->flags.parsed || path == NULL) {
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+ if (token == NULL) {
|
|
|
+ token = lwjson_get_first_token(lwobj);
|
|
|
+ }
|
|
|
+ if (token == NULL || (token->type != LWJSON_TYPE_ARRAY && token->type != LWJSON_TYPE_OBJECT)) {
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+ return prv_find(token, path);
|
|
|
+}
|