2024-04-22 00:09:51 +08:00

2014 lines
52 KiB
C

/*********************************************************************************************************
* ------------------------------------------------------------------------------------------------------
* file description
* ------------------------------------------------------------------------------------------------------
* \file json.c
* \unit json
* \brief This is a C language version of json streamlined parser
* \author Lamdonn
* \version v1.0.0
* \license GPL-2.0
* \copyright Copyright (C) 2023 Lamdonn.
********************************************************************************************************/
#include "json.h"
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <math.h>
#include <float.h>
/* dump buffer define */
typedef struct
{
char* address; /**< buffer base address */
unsigned int size; /**< size of buffer */
unsigned int end; /**< end of buffer used */
} BUFFER;
/* json define */
typedef struct JSON {
struct JSON* next; /**< next json */
char* key; /**< the key of json is empty when the type is array */
int type; /**< json base type, @ref JSON_TYPE_xxx */
union
{
int bool_; /**< bool type */
double float_; /**< float number type */
int int_; /**< int number type */
char* string_; /**< string type */
struct JSON* child_; /**< array or object type */
} value;
} JSON;
static const char* lbegin = 0; /**< beginning of line */
static int eline = 0; /**< line of error message */
static int ecolumn = 0; /**< column of error message */
static int etype = 0; /**< type of error message */
/* predeclare these prototypes. */
static const char* parse_text(json_t json, const char* text);
static int print_json(json_t json, BUFFER* buf, int depth, int format);
/* set error message and type */
#define E(type) (etype=(type),ecolumn=text-lbegin)
/**
* \brief for analysing failed parses
* \param[out] line: error line
* \param[out] column: error column
* \return error type
*/
int json_error_info(int* line, int* column)
{
/* No error occurred, return directly */
if (etype == JSON_E_OK) return JSON_E_OK;
/* Output the line and column where the error is located */
if (line) *line = eline;
if (column) *column = ecolumn;
/* Return error type */
return etype;
}
/**
* \brief Compare two strings case-insensitively.
*
* \param[in] s1 The first string to compare
* \param[in] s2 The second string to compare
*
* \return Returns an integer less than, equal to, or greater than zero if s1 is found, respectively,
* to be less than, to match, or be greater than s2.
*/
static int json_strccmp(const char* s1, const char* s2)
{
if (!s1) return (s1 == s2) ? 0 : 1;
if (!s2) return 1;
/* Compare the similarities and differences of characters one by one */
for (; tolower(*s1) == tolower(*s2); ++s1, ++s2)
{
if (*s1 == 0) return 0;
}
return tolower(*(const unsigned char*)s1) - tolower(*(const unsigned char*)s2);
}
/**
* \brief Duplicate a given string.
*
* \param[in] str String to be duplicated.
* \param[in] len Length of the string.
* \return Pointer to the duplicated string if successful, NULL otherwise.
*/
static char* json_strdup(const char* str, int len)
{
char* s;
/* Allocate memory for the new string */
s = (char*)malloc(len + 1);
if (!s) return NULL;
/* Copy the given string into the allocated memory */
memcpy(s, str, len);
s[len] = '\0';
return s;
}
/**
* \brief get the smallest power of 2 not greater than x.
* \param[in] x: positive integer
* \return the smallest power of 2 not greater than x
*/
static int pow2gt(int x)
{
int b = sizeof(int) * 8;
int i = 1;
--x;
while (i < b) { x |= (x >> i); i <<= 1; }
return x + 1;
}
/**
* \brief confirm whether buf still has the required capacity, otherwise add capacity.
* \param[in] buf: buf handle
* \param[in] needed: required capacity
* \return 1 success or 0 fail
*/
static int expansion(BUFFER *buf, unsigned int needed)
{
char* address;
int size;
if (!buf || !buf->address) return 0;
needed += buf->end;
if (needed <= buf->size) return 1; /* there is still enough space in the current buf */
size = pow2gt(needed);
address = (char*)realloc(buf->address, size);
if (!address) return 0;
buf->size = size;
buf->address = address;
return 1;
}
#define buf_append(n) expansion(buf, (n)) /* append n size space for buf */
#define buf_putc(c) (buf->address[buf->end++]=(c)) /* put a non zero character into buf */
#define buf_end() (buf->address[buf->end]) /* obtain the tail of buf */
static unsigned int parse_hex4(const char* str)
{
unsigned int h = 0;
if (*str >= '0' && *str <= '9') h += (*str) - '0';
else if (*str >= 'A' && *str <= 'F') h += 10 + (*str) - 'A';
else if (*str >= 'a' && *str <= 'f') h += 10 + (*str) - 'a';
else return 0;
h = h << 4; str++;
if (*str >= '0' && *str <= '9') h += (*str) - '0';
else if (*str >= 'A' && *str <= 'F') h += 10 + (*str) - 'A';
else if (*str >= 'a' && *str <= 'f') h += 10 + (*str) - 'a';
else return 0;
h = h << 4; str++;
if (*str >= '0' && *str <= '9') h += (*str) - '0';
else if (*str >= 'A' && *str <= 'F') h += 10 + (*str) - 'A';
else if (*str >= 'a' && *str <= 'f') h += 10 + (*str) - 'a';
else return 0;
h = h << 4; str++;
if (*str >= '0' && *str <= '9') h += (*str) - '0';
else if (*str >= 'A' && *str <= 'F') h += 10 + (*str) - 'A';
else if (*str >= 'a' && *str <= 'f') h += 10 + (*str) - 'a';
else return 0;
return h;
}
/**
* \brief Convert and write a UTF-8 character sequence to an output buffer.
*
* \param[in] in The input string containing the UTF-8 sequence
* \param[in,out] out The output buffer to write the converted UTF-8 sequence
*/
static void json_utf(const char **in, char **out)
{
const char* ptr = *in;
char* ptr2 = *out;
int len = 0;
unsigned int uc, uc2;
unsigned char mask_first_byte[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC };
uc = parse_hex4(ptr + 1); ptr += 4; /* get the unicode char. */
if ((uc >= 0xDC00 && uc <= 0xDFFF) || uc == 0) return; /* check for invalid. */
if (uc >= 0xD800 && uc <= 0xDBFF) /* UTF16 surrogate pairs. */
{
if (ptr[1] != '\\' || ptr[2] != 'u') return; /* missing second-half of surrogate */
uc2 = parse_hex4(ptr + 3);
ptr += 6;
if (uc2 < 0xDC00 || uc2>0xDFFF) return; /* invalid second-half of surrogate */
uc = 0x10000 + (((uc & 0x3FF) << 10) | (uc2 & 0x3FF));
}
len = 4;
if (uc < 0x80) len = 1;
else if (uc < 0x800) len = 2;
else if (uc < 0x10000) len = 3;
ptr2 += len;
switch (len)
{
case 4: *--ptr2 = ((uc | 0x80) & 0xBF); uc >>= 6;
case 3: *--ptr2 = ((uc | 0x80) & 0xBF); uc >>= 6;
case 2: *--ptr2 = ((uc | 0x80) & 0xBF); uc >>= 6;
case 1: *--ptr2 = (uc | mask_first_byte[len]);
}
ptr2 += len;
}
/**
* \brief Skip leading whitespace characters in a string, including newline characters.
*
* \param[in] in The input string to be skipped
*
* \return Returns a pointer to the first non-whitespace character in the string
*/
static const char* skip(const char* in)
{
while (in && *in && (unsigned char)*in <= ' ')
{
/* when a newline character is encountered, record the current parsing line */
if (*in == '\n')
{
eline++;
lbegin = in; /* Record line start position */
}
in++;
}
return in;
}
/**
* \brief create a null json object.
* \return json handle or NULL fail
*/
json_t json_create(void)
{
json_t json;
/* Allocate json structure space and initialize */
json = (json_t)malloc(sizeof(JSON));
if (json) memset(json, 0, sizeof(JSON));
return json;
}
/**
* \brief delete the json entity and its sub-entities.
* \param[in] json: json handle
* \return none
*/
void json_delete(json_t json)
{
json_t next;
while (json)
{
next = json->next;
/* For arrays or object types, recursively delete child json */
if (json->type == JSON_TYPE_ARRAY || json->type == JSON_TYPE_OBJECT) json_delete(json->value.child_);
/* String type, then free the string */
else if (json->type == JSON_TYPE_STRING) free(json->value.string_);
/* Free the key of json */
if (json->key) free(json->key);
/* Delete self */
free(json);
json = next;
}
}
/**
* \brief get the number of children of a json array or object.
* \param[in] json: json handle
* \return json size
*/
int json_size(json_t json)
{
json_t c;
int i = 0;
if (!json) return 0;
/* Only array and object have child objects */
if (json->type != JSON_TYPE_ARRAY && json->type != JSON_TYPE_OBJECT) return 0;
/* Traverse for statistics */
c = json->value.child_;
while (c)
{
i++;
c = c->next;
}
return i;
}
/**
* \brief get the type of json.
* \param[in] json: json handle
* \return json type
*/
int json_type(json_t json)
{
if (!json) return JSON_TYPE_UNKNOW;
return json->type;
}
/**
* \brief get the key of json.
* \param[in] json: json handle
* \return json key
*/
const char* json_key(json_t json)
{
if (!json) return NULL;
return json->key;
}
/**
* \brief get the bool of json.
* \param[in] json: json handle
* \return json bool
*/
int json_value_bool(json_t json)
{
if (!json) return 0;
if (json->type != JSON_TYPE_BOOL) return 0;
return json->value.bool_;
}
/**
* \brief get the int of json.
* \param[in] json: json handle
* \return json int
*/
int json_value_int(json_t json)
{
if (!json) return 0;
if (json->type != JSON_TYPE_INT) return 0;
return json->value.int_;
}
/**
* \brief get the float of json.
* \param[in] json: json handle
* \return json float
*/
double json_value_float(json_t json)
{
if (!json) return 0.0;
if (json->type != JSON_TYPE_FLOAT) return 0.0;
return json->value.float_;
}
/**
* \brief get the string of json.
* \param[in] json: json handle
* \return json string
*/
const char* json_value_string(json_t json)
{
if (!json) return NULL;
if (json->type != JSON_TYPE_STRING) return NULL;
return json->value.string_;
}
/**
* \brief get the array of json.
* \param[in] json: json handle
* \return json array
*/
json_t json_value_array(json_t json)
{
if (!json) return NULL;
if (json->type != JSON_TYPE_ARRAY) return NULL;
return json->value.child_;
}
/**
* \brief get the object of json.
* \param[in] json: json handle
* \return json object
*/
json_t json_value_object(json_t json)
{
if (!json) return NULL;
if (json->type != JSON_TYPE_OBJECT) return NULL;
return json->value.child_;
}
/**
* \brief set the key of json.
* \param[in] json: json handle
* \param[in] key: new key
* \return json itself success or NULL fail
*/
json_t json_set_key(json_t json, const char* key)
{
char* k;
if (!json) return NULL;
/* The current key and the one to be set can be the same, and can be set successfully directly */
if (json->key && (json->key == key || !strcmp(json->key, key))) return json;
/* If the passed in key is not empty, duplicate a backup */
if (key)
{
k = json_strdup(key, strlen(key));
if (!k) return NULL;
}
/* Otherwise, clear the json key */
else k = NULL;
/* Release the old key to update the new one */
if (json->key) free(json->key);
json->key = k;
return json;
}
/**
* \brief set the value of json to null.
* \param[in] json: json handle
* \return json itself success or NULL fail
*/
json_t json_set_null(json_t json)
{
if (!json) return NULL;
/* delete string value */
if (json->type == JSON_TYPE_STRING) free(json->value.string_);
/* delete child objects */
else if (json->type == JSON_TYPE_ARRAY || json->type == JSON_TYPE_OBJECT) json_delete(json->value.child_);
/* Change the type to null and reset the value */
json->type = JSON_TYPE_NULL;
memset(&json->value, 0, sizeof(json->value));
return json;
}
/**
* \brief set the value of json to bool.
* \param[in] json: json handle
* \param[in] b: new bool
* \return json itself success or NULL fail
*/
json_t json_set_bool(json_t json, int b)
{
if (!json) return NULL;
/* If the current type does not match, set the type to null first */
if (json->type != JSON_TYPE_BOOL) json_set_null(json);
/* Change the type to bool and set the bool value */
json->type = JSON_TYPE_BOOL;
json->value.bool_ = (b == JSON_FALSE ? JSON_FALSE : JSON_TRUE);
return json;
}
/**
* \brief set the value of json to int.
* \param[in] json: json handle
* \param[in] num: new number
* \return json itself success or NULL fail
*/
json_t json_set_int(json_t json, int num)
{
if (!json) return NULL;
/* If the current type does not match, set the type to null first */
if (json->type != JSON_TYPE_INT || json->type != JSON_TYPE_FLOAT) json_set_null(json);
/* Change the type to int and set the int value */
json->type = JSON_TYPE_INT;
json->value.int_ = num;
return json;
}
/**
* \brief set the value of json to float.
* \param[in] json: json handle
* \param[in] num: new number
* \return json itself success or NULL fail
*/
json_t json_set_float(json_t json, double num)
{
if (!json) return NULL;
/* If the current type does not match, set the type to null first */
if (json->type != JSON_TYPE_INT || json->type != JSON_TYPE_FLOAT) json_set_null(json);
/* Change the type to float and set the float value */
json->type = JSON_TYPE_FLOAT;
json->value.float_ = num;
return json;
}
/**
* \brief set the value of json to string.
* \param[in] json: json handle
* \param[in] string: new string
* \return json itself success or NULL fail
*/
json_t json_set_string(json_t json, const char* string)
{
char* s;
if (!json) return NULL;
/* If the current type does not match, set the type to null first */
if (json->type != JSON_TYPE_STRING) json_set_null(json);
/* Change the type to string */
json->type = JSON_TYPE_STRING;
/* The current string and the one to be set can be the same, and can be set successfully directly */
if (json->value.string_ && (json->value.string_ == string || !strcmp(json->value.string_, string))) return json;
/* If the passed in string is not empty, duplicate a backup */
if (string)
{
s = json_strdup(string, strlen(string));
if (!s) return NULL;
}
/* Otherwise, clear the json string */
else s = NULL;
/* Release the old string to update the new one */
if (json->value.string_) free(json->value.string_);
json->value.string_ = s;
return json;
}
/**
* \brief set the value of json to array.
* \param[in] json: json handle
* \param[in] object: object handle
* \return json itself success or NULL fail
*/
json_t json_set_object(json_t json, json_t object)
{
if (!json) return NULL;
/* If the current type does not match, set the type to null first */
if (json->type != JSON_TYPE_OBJECT) json_set_null(json);
/* Change the type to object */
json->type = JSON_TYPE_OBJECT;
/* The current object and the one to be set can be the same, and can be set successfully directly */
if (json->value.child_ && json->value.child_ == object) return json;
/* Release the old object to update the new one */
if (json->value.child_) json_delete(json->value.child_);
json->value.child_ = object;
return json;
}
/**
* \brief set the value of json to array.
* \param[in] json: json handle
* \param[in] array: array handle
* \return json itself success or NULL fail
*/
json_t json_set_array(json_t json, json_t array)
{
if (!json) return NULL;
/* If the current type does not match, set the type to null first */
if (json->type != JSON_TYPE_ARRAY) json_set_null(json);
/* Change the type to array */
json->type = JSON_TYPE_ARRAY;
/* The current array and the one to be set can be the same, and can be set successfully directly */
if (json->value.child_ && json->value.child_ == array) return json;
/* Release the old array to update the new one */
if (json->value.child_) json_delete(json->value.child_);
json->value.child_ = array;
return json;
}
/**
* \brief set the int values of json to array.
* \param[in] json: json handle
* \param[in] numbers: array
* \param[in] count: size of array
* \return json itself success or NULL fail
*/
json_t json_set_array_int(json_t json, const int* numbers, int count)
{
json_t array = NULL, n = NULL, p = NULL;
int i;
/* Input parameter validity check */
if (!json) return NULL;
if (!numbers) return NULL;
if (count <= 0) return NULL;
/* Add data one by one to the array */
for (i = 0; i < count; i++)
{
/* Create a single json data item */
n = json_create();
if (!n)
{
json_delete(array);
return NULL;
}
/* Set array items as input data */
json_set_int(n, numbers[i]);
/* When i is 0, which is the first item of the array, the json array points to it */
if (!i) array = n;
/* When it is not the first item, link it directly */
else p->next = n;
p = n;
}
/* Set the current json to generate an array */
json_set_array(json, array);
return json;
}
/**
* \brief set the float values of json to array.
* \param[in] json: json handle
* \param[in] numbers: array
* \param[in] count: size of array
* \return json itself success or NULL fail
*/
json_t json_set_array_float(json_t json, const float* numbers, int count)
{
json_t array = NULL, n = NULL, p = NULL;
int i;
/* Input parameter validity check */
if (!json) return NULL;
if (!numbers) return NULL;
if (count <= 0) return NULL;
/* Add data one by one to the array */
for (i = 0; i < count; i++)
{
/* Create a single json data item */
n = json_create();
if (!n)
{
json_delete(array);
return NULL;
}
/* Set array items as input data */
json_set_float(n, numbers[i]);
/* When i is 0, which is the first item of the array, the json array points to it */
if (!i) array = n;
/* When it is not the first item, link it directly */
else p->next = n;
p = n;
}
/* Set the current json to generate an array */
json_set_array(json, array);
return json;
}
/**
* \brief set the double values of json to array.
* \param[in] json: json handle
* \param[in] numbers: array
* \param[in] count: size of array
* \return json itself success or NULL fail
*/
json_t json_set_array_double(json_t json, const double* numbers, int count)
{
json_t array = NULL, n = NULL, p = NULL;
int i;
/* Input parameter validity check */
if (!json) return NULL;
if (!numbers) return NULL;
if (count <= 0) return NULL;
/* Add data one by one to the array */
for (i = 0; i < count; i++)
{
/* Create a single json data item */
n = json_create();
if (!n)
{
json_delete(array);
return NULL;
}
/* Set array items as input data */
json_set_float(n, numbers[i]);
/* When i is 0, which is the first item of the array, the json array points to it */
if (!i) array = n;
/* When it is not the first item, link it directly */
else p->next = n;
p = n;
}
/* Set the current json to generate an array */
json_set_array(json, array);
return json;
}
/**
* \brief set the string values of json to array.
* \param[in] json: json handle
* \param[in] strings: array
* \param[in] count: size of array
* \return json itself success or NULL fail
*/
json_t json_set_array_string(json_t json, const char** strings, int count)
{
json_t array = NULL, n = NULL, p = NULL;
int i;
/* Input parameter validity check */
if (!json) return NULL;
if (!strings) return NULL;
if (count <= 0) return NULL;
/* Add data one by one to the array */
for (i = 0; i < count; i++)
{
/* Create a single json data item */
n = json_create();
if (!n)
{
json_delete(array);
return NULL;
}
/* Set array items as input data.
* When designing new space allocation using `json_set_string()`, there may be allocation failures.
* Space allocation failed, we need to delete all the previously created json objects.
*/
if (!json_set_string(n, strings[i]))
{
json_delete(n);
json_delete(array);
return NULL;
}
/* When i is 0, which is the first item of the array, the json array points to it */
if (!i) array = n;
/* When it is not the first item, link it directly */
else p->next = n;
p = n;
}
/* Set the current json to generate an array */
json_set_array(json, array);
return json;
}
/**
* \brief Get the child element of a json object or array based on the specified key or index.
*
* \param[in] json The json object or array
* \param[in] key The key to search for (ignored for json arrays)
* \param[in] index The index of the child element to retrieve
* \param[out] out_prev Optional pointer to store the previous child element
*
* \return Returns the child element at the specified key or index, or NULL if not found or invalid input
*/
static json_t json_get_child(json_t json, const char* key, int index, json_t *out_prev)
{
json_t c, prev = NULL;
if (!json) return NULL;
/* json of value type cannot have a key */
if (key && json->type == JSON_TYPE_ARRAY) return NULL;
/* Only array and object have child objects */
if (json->type != JSON_TYPE_ARRAY && json->type != JSON_TYPE_OBJECT) return NULL;
/* Traversing and matching json that match */
c = json->value.child_;
while (c)
{
/* When no key is specified, only update the next match based on the index.
* When specifying the key, it is necessary to update the next match only when the key is the same.
*/
if (!key || !json_strccmp(c->key, key))
{
index--;
if (index < 0) break; /* Out of valid indexes, exit matching */
}
/* Update traversal link */
prev = c;
c = c->next;
}
/* The index is still used up, which means it doesn't match the specified one */
if (index >= 0) return NULL;
/* The matching json object is invalid, exit directly */
if (!c) return NULL;
/* Output, matching the previous object of json */
if (out_prev) *out_prev = prev;
return c;
}
/**
* \brief Get the child element of a json object or array based on the specified key or index.
* When no key is specified, only update the next match based on the index.
* When specifying the key, it is necessary to update the next match only when the key is the same.
*
* \param[in] json The json object or array
* \param[in] key The key to search for (ignored for json arrays)
* \param[in] index The index of the child element to retrieve
*
* \return Returns the child element at the specified key or index, or NULL if not found or invalid input
*/
json_t json_get(json_t json, const char* key, int index)
{
return json_get_child(json, key, index, NULL);
}
/**
* \brief get the child of json object by index continuously.
* \param[in] json: json handle
* \param[in] index: index
* \param[in] ...: unexposed parameters, continuously enter index until the INT_MIN ends
* \return the specify child json object, or NULL fail
*/
json_t json_to_index_valist(json_t json, int index, ...)
{
json_t c = json;
va_list args;
int i = index;
if (!json) return NULL;
/* First time get */
c = json_get(c, NULL, i);
if (!c) return NULL;
va_start(args, index);
/* Continuously obtain the index of indefinite parameter inputs to recursively obtain json sub objects */
i = va_arg(args, int);
while (c && i != INT_MIN) /* Stop getting when the index is `INT_MIN` */
{
c = json_get(c, NULL, i);
i = va_arg(args, int);
}
va_end(args);
return c;
}
/**
* \brief get the child of json object by key continuously.
* \param[in] json: json handle
* \param[in] key: address of key
* \param[in] ...: unexposed parameters, continuously enter key until the NULL ends
* \return the specify child json object, or NULL fail
*/
json_t json_to_key_valist(json_t json, char* key, ...)
{
json_t c = json;
va_list args;
char* s = key;
if (!json) return NULL;
/* First time get */
c = json_get(c, s, 0);
if (!c) return c;
va_start(args, key);
/* Continuously obtain the index of indefinite parameter inputs to recursively obtain json sub objects */
s = va_arg(args, char*);
while (c && s != NULL) /* Stop getting when the key is `NULL` */
{
c = json_get(c, s, 0);
s = va_arg(args, char*);
}
va_end(args);
return c;
}
/**
* \brief insert a json object inito json by index.
* \param[in] json: json handle
* \param[in] index: index
* \param[in] item: another json handle
* \return ins itself success or NULL fail
*/
json_t json_attach(json_t json, int index, json_t ins)
{
json_t c;
json_t prev = NULL;
/* Input parameter validity check */
if (!json) return NULL;
if (!ins) return NULL;
/* Check if the `ins` has a key and determine if it matches the type of `json` */
if (!(json->type == JSON_TYPE_ARRAY && !ins->key) && !(json->type == JSON_TYPE_OBJECT && ins->key)) return NULL;
/* Traverse and iterate to the specified index */
c = json->value.child_;
while (c && index > 0)
{
prev = c;
c = c->next;
index--;
}
/* When the json child object is still empty, ins is taken as the first item */
if (!c && !prev)
{
json->value.child_ = ins;
return ins;
}
/* Link ins to its child */
if (prev) prev->next = ins;
ins->next = c;
/* Head insertion */
if (c == json->value.child_) json->value.child_ = ins;
return ins;
}
/**
* \brief detach json object by key.
* \param[in] json: json handle
* \param[in] key: address of key
* \return detached object success or NULL fail
*/
json_t json_detach(json_t json, const char* key, int index)
{
json_t c, prev = NULL;
/* Getting for json object that require detach */
c = json_get_child(json, key, index, &prev);
if (!c) return NULL;
/* Detach `c` from the child linked list */
if (prev) prev->next = c->next;
if (c == json->value.child_) json->value.child_ = c->next;
/* Rest `c` link */
c->next = NULL;
return c;
}
/**
* \brief copy json with options.
* \param[in] json: json handle
* \param[in] recurse: whether to copy the sub-object, 0-no, other-yes
* \return new json object
*/
json_t json_copy(json_t json)
{
json_t copy, cptr, nptr = NULL, child;
if (!json) return NULL;
/* Create a backup empty json object */
copy = json_create();
if (!copy) return NULL;
copy->type = json->type;
copy->value = json->value;
/* If it is a string type, copy the string */
if (json->type == JSON_TYPE_STRING && json->value.string_)
{
copy->value.string_ = json_strdup(json->value.string_, strlen(json->value.string_));
if (!copy->value.string_)
{
json_delete(copy);
return NULL;
}
}
/* If there is a key, copy the key */
if (json->key)
{
copy->key = json_strdup(json->key, strlen(json->key));
if (!copy->key)
{
json_delete(copy);
return NULL;
}
}
/* walk the ->next chain for the child. */
if (json->type == JSON_TYPE_OBJECT || json->type == JSON_TYPE_ARRAY)
{
cptr = json->value.child_;
while (cptr)
{
/* copy (with recurse) each json in the ->next chain */
child = json_copy(cptr);
if (!child)
{
json_delete(copy);
return NULL;
}
/* if newitem->child already set, then crosswire ->prev and ->next and move on */
if (nptr)
{
nptr->next = child;
nptr = child;
}
/* set newitem->child and move to it */
else
{
copy->value.child_ = child;
nptr = child;
}
cptr = cptr->next;
}
}
return copy;
}
/**
* \brief Parse and convert a number in a json string.
*
* \param[in,out] json The json object to store the parsed number
* \param[in] text The input string containing the number
*
* \return Returns a pointer to the next character after the parsed number in the string, or NULL if there was an error in parsing the number
*/
static const char* parse_number(json_t json, const char* text)
{
double number = 0; /* Converted number */
int sign = 1, scale = 0; /* Sign and scale of integer part */
int e_sign = 1, e_scale = 0; /* Sign and scale of exponent part */
int isint = 1; /* Flag for whether there is only an integer part within a string */
/* First, check if there is a negative number with `-` before it */
if (*text == '-')
{
sign = -1;
text++;
/* Check if the first character is a valid number */
if (!(*text >= '0' && *text <= '9'))
{
E(JSON_E_VALUE);
return NULL;
}
}
/* Skip invalid `0` in the previous section */
while (*text == '0') text++;
/* Integer part */
if (*text >= '1' && *text <= '9')
{
do
{
number = (number * 10.0) + (*text++ - '0'); /* carry addition */
} while (*text >= '0' && *text <= '9');
}
/* Decimal part */
if (*text == '.')
{
text++;
/* Check if the first character is a valid number */
if (!(*text >= '0' && *text <= '9'))
{
E(JSON_E_VALUE);
return NULL;
}
/* The number of decimal parts is also increased by 10 times first, and then reduced according to the scale later on */
do
{
number = (number * 10.0) + (*text++ - '0');
scale--;
} while (*text >= '0' && *text <= '9');
/* Decimal part present, marked as non integer */
isint = 0;
}
/* Exponent part */
if (*text == 'e' || *text == 'E')
{
text++;
/* Symbol `+` skip */
if (*text == '+') text++;
/* Symbol `-` with sign */
else if (*text == '-')
{
e_sign = -1;
text++;
}
/* Check if the first character is a valid number */
if (!(*text >= '0' && *text <= '9'))
{
E(JSON_E_VALUE);
return NULL;
}
/* Conversion exponent part */
while (*text >= '0' && *text <= '9')
{
e_scale = (e_scale * 10) + (*text++ - '0'); /* number */
}
/* Exponent part present, marked as non integer */
isint = 0;
}
/* Calculated conversion result */
number = (double)sign * number * pow(10.0, (scale + e_scale * e_sign));
/* As an integer and within the scope of an integer */
if (isint && INT_MIN <= number && number <= INT_MAX)
{
json->type = JSON_TYPE_INT;
json->value.int_ = (int)number;
}
/* As a floating-point number */
else
{
json->type = JSON_TYPE_FLOAT;
json->value.float_ = number;
}
return text;
}
/**
* \brief parse the input text to buffer, and fill the results into buf.
* \param[in] text: number text
* \param[out] buf: the address used to receive the parsed string pointer
* \return the new address of the transformed text
*/
static const char* parse_string_buffer(char** buf, const char* text)
{
const char* ptr = text + 1;
char* ptr2;
char* out;
int len = 0;
*buf = NULL;
/* Not a string! */
if (*text != '\"')
{
E(JSON_E_INVALID);
return NULL;
}
/* Get the length of the string */
while (*ptr && *ptr != '\"')
{
if (*ptr++ == '\\') ptr++; /* skip escaped quotes. */
len++;
}
/* Allocate storage space based on the calculated string length */
out = (char*)malloc(len + 1);
if (!out)
{
E(JSON_E_MEMORY);
return NULL;
}
/* Copy text to new space */
ptr = text + 1;
ptr2 = out;
while (*ptr && *ptr != '\"')
{
/* Normal character */
if (*ptr != '\\') { *ptr2++ = *ptr++; }
/* Escape character */
else
{
ptr++;
if (*ptr == 'b') { *ptr2++ = '\b'; }
else if (*ptr == 'f') { *ptr2++ = '\f'; }
else if (*ptr == 'n') { *ptr2++ = '\n'; }
else if (*ptr == 'r') { *ptr2++ = '\r'; }
else if (*ptr == 't') { *ptr2++ = '\t'; }
else if (*ptr == 'u') { json_utf(&ptr, &ptr2); }
else { *ptr2++ = *ptr; }
ptr++;
}
}
*ptr2 = 0;
if (*ptr == '\"') ptr++;
*buf = out;
return ptr;
}
/**
* \brief parse the input text and fill the result into json.
* \param[in,out] json: json handle
* \param[in] text: string text
* \return the new address of the transformed text
*/
static const char* parse_string(json_t json, const char* text)
{
json->value.string_ = NULL;
json->type = JSON_TYPE_STRING;
return parse_string_buffer(&(json->value.string_), text);
}
/**
* \brief Parse an array in a json string and create a corresponding json object.
*
* \param[in,out] json The json object to store the parsed array
* \param[in] text The input string containing the array
*
* \return Returns a pointer to the next character after the parsed array in the string, or NULL if there was an error in parsing the array
*/
static const char* parse_array(json_t json, const char* text)
{
json_t child, prev = NULL;
/* Not an array! */
if (*text != '[')
{
E(JSON_E_INVALID);
return NULL;
}
/* First, record the json type as array */
json->type = JSON_TYPE_ARRAY;
/* Skip useless characters */
text = skip(text + 1);
/* Encountered `]`, indicating that it is an empty array, return directly */
if (*text == ']') return text + 1;
/* Loop parsing each member of an array */
do
{
/* skip ',' */
if (prev) text++;
/* Create json objects as array member */
child = json_create();
if (!child)
{
E(JSON_E_MEMORY);
return NULL;
}
/* skip meaningless character parsing. */
text = skip(parse_text(child, skip(text)));
/* parse_text has already logged the error message */
if (!text)
{
if (child) json_delete(child);
return NULL;
}
/* Linking array members to an array linked list */
if (prev) prev->next = child;
else json->value.child_ = child;
/* Update the previous member's pointing */
prev = child;
} while (*text == ','); /* Array members are separated by the symbol `,` */
/* Not a complete array, not formed as a closed `[]` */
if (*text != ']')
{
E(JSON_E_SQUARE);
return NULL;
}
return text + 1; /* end of array */
}
/**
* \brief Parse an object in a json string and create a corresponding json object.
*
* \param[out] json The json object to store the parsed object
* \param[in] text The input string containing the object
*
* \return Returns a pointer to the next character after the parsed object in the string, or NULL if there was an error in parsing the object
*/
static const char* parse_object(json_t json, const char* text)
{
char* key = NULL;
json_t child, prev = NULL;
/* Not an object! */
if (*text != '{')
{
E(JSON_E_INVALID);
return NULL;
}
/* First, record the json type as object */
json->type = JSON_TYPE_OBJECT;
/* Skip useless characters */
text = skip(text + 1);
/* Encountered `}`, indicating that it is an empty object, return directly */
if (*text == '}') return text + 1;
/* Loop parsing each member of an object */
do
{
/* skip ',' */
if (prev) text++;
/* First, parse the key section */
text = skip(parse_string_buffer(&key, skip(text)));
if (!text)
{
if (key) free(key);
E(JSON_E_VALUE);
return NULL;
}
/* Not the correct key-value delimiter */
if (*text != ':') /* fail! */
{
if (key) free(key);
E(JSON_E_KEY);
return NULL;
}
/* Create json objects as object member */
child = json_create();
if (!child)
{
E(JSON_E_MEMORY);
return NULL;
}
/* skip any spacing, get the text. */
text = skip(parse_text(child, skip(text + 1)));
/* parse_text has already logged the error message */
if (!text)
{
if (key) free(key);
if (child) json_delete(child);
return NULL;
}
/* Point to key */
child->key = key;
/* Linking array members to an object linked list */
if (prev) prev->next = child;
else json->value.child_ = child;
/* Update the previous member's pointing */
prev = child;
} while (*text == ','); /* Object members are separated by the symbol `,` */
/* Not a complete object, not formed as a closed `{}` */
if (*text != '}') { E(JSON_E_CURLY); return NULL; }
return text + 1; /* end of object */
}
/**
* \brief Parse the text part of a json value in a json string.
*
* \param[in,out] json The json object to store the parsed value
* \param[in] text The input string containing the text part of the value
*
* \return Returns a pointer to the next character after the parsed value in the string, or NULL if there was an error in parsing the value
*/
static const char* parse_text(json_t json, const char* text)
{
if (!strncmp(text, "null", 4))
{
json->type = JSON_TYPE_NULL;
return text + 4;
}
if (!strncmp(text, "false", 5))
{
json->type = JSON_TYPE_BOOL;
json->value.bool_ = JSON_FALSE;
return text + 5;
}
if (!strncmp(text, "true", 4))
{
json->type = JSON_TYPE_BOOL;
json->value.bool_ = JSON_TRUE;
return text + 4;
}
if (*text == '-' || (*text >= '0' && *text <= '9')) return parse_number(json, text);
if (*text == '\"') return parse_string(json, text);
if (*text == '[') return parse_array(json, text);
if (*text == '{') return parse_object(json, text);
E(JSON_E_INVALID);
return NULL;
}
/**
* \brief convert numbers in json to text and append to buf.
* \param[in] json: json handle
* \param[in] buf: buf handle
* \return 1 success or 0 fail
*/
static int print_number(json_t json, BUFFER* buf)
{
double num = json->value.float_;
int len = 0;
/* Number is 0 */
if (num == 0)
{
if (!buf_append(1)) return 0;
buf_putc('0');
}
/* The number type is an integer type */
else if (json->type == JSON_TYPE_INT)
{
if (!buf_append(20)) return 0;
len = sprintf(&buf_end(), "%d", (int)json->value.int_);
buf->end += len;
}
/* The number type is a floating point type */
else if (json->type == JSON_TYPE_FLOAT)
{
if (!buf_append(64)) return 0;
/* use full transformation within bounded space */
if (fabs(floor(num) - num) <= DBL_EPSILON && fabs(num) < 1.0e60) len = sprintf(&buf_end(), "%.1lf", num);
/* use exponential form conversion beyond the limited range */
else if (fabs(num) < 1.0e-6 || fabs(num) > 1.0e9) len = sprintf(&buf_end(), "%e", num);
/* default conversion */
else
{
len = sprintf(&buf_end(), "%lf", num);
while (len > 0 && (&buf_end())[len-1] == '0' && (&buf_end())[len-2] != '.') len--; /* remove the invalid 0 in the decimal part */
}
buf->end += len;
}
/* Not of number type */
else return 0;
return 1;
}
/**
* \brief store c string conversion to buf.
* \param[in] str: address of string
* \param[in] buf: buf handle
* \return 1 success or 0 fail
*/
static int print_string_buffer(const char* str, BUFFER* buf)
{
const char* p;
int len = 0, escape = 0;
/* Empty string */
if (!str)
{
if (!buf_append(2)) return 0;
buf_putc('\"');
buf_putc('\"');
return 1;
}
/* Get the length of string */
p = str;
while (*p)
{
len++;
if (*p == '\"' || *p == '\\' || *p == '\b' || *p == '\f' || *p == '\n' || *p == '\r' || *p == '\t') /* escape character */
{
escape = 1;
len++;
}
else if ((unsigned char)(*p) < ' ') /* control character */
{
escape = 1;
len += 5; // utf
}
p++;
}
if (!buf_append(len + 2)) return 0; // \" \"
buf_putc('\"');
/* Without escape characters */
if (!escape)
{
while (len--) buf_putc(*str++);
buf_putc('\"');
return 1;
}
p = str;
while (*p)
{
if ((unsigned char)(*p) >= ' ' && *p != '\"' && *p != '\\')
{
buf_putc(*p++);
}
else
{
/* escape and print */
buf_putc('\\');
if (*p == '\\') buf_putc('\\');
else if (*p == '\"') buf_putc('\"');
else if (*p == '\b') buf_putc('b');
else if (*p == '\f') buf_putc('f');
else if (*p == '\n') buf_putc('n');
else if (*p == '\r') buf_putc('r');
else if (*p == '\t') buf_putc('t');
else
{
sprintf(&buf_end(), "u%04x", (unsigned char)(*p));
buf->end += 5;
}
p++;
}
}
buf_putc('\"');
return 1;
}
/**
* \brief store json string conversion to buf.
* \param[in] json: json handle
* \param[in] buf: buf handle
* \return 1 success or 0 fail
*/
static int print_string(json_t json, BUFFER* buf)
{
return print_string_buffer(json->value.string_, buf);
}
/**
* \brief render a array to text.
* \param[in] json: json handle
* \param[in] buf: buf handle
* \param[in] depth: print depth, indentation
* \param[in] format: 0 gives unformatted, otherwise gives formatted
* \return 1 success or 0 fail
*/
static int print_array(json_t json, BUFFER* buf, int depth, int format)
{
int i = 0, count = 0;
json_t child = json->value.child_;
/* Empty array */
if (!child)
{
if (!buf_append(2)) return 0;
buf_putc('[');
buf_putc(']');
return 1;
}
if (format)
{
while (child)
{
/* check if there are arrays or objects in the children */
if ((child->type == JSON_TYPE_ARRAY || child->type == JSON_TYPE_OBJECT) && child->value.child_)
{
count++;
break;
}
child = child->next;
}
}
if (!buf_append((format && count) ? 2 : 1)) return 0;
buf_putc('[');
if (format && count) buf_putc('\n');
/* print children */
child = json->value.child_;
while (child)
{
/* print starting indent */
if (format && count)
{
if (!buf_append(depth + 1)) return 0;
for (i = 0; i < depth + 1; i++) buf_putc('\t');
}
/* print value */
if (!print_json(child, buf, depth + 1, format)) return 0;
/* print member separator ',' */
if (child->next)
{
if (!buf_append(format ? 2 : 1)) return 0;
buf_putc(',');
if (format)
{
if (count) buf_putc('\n');
else buf_putc(' ');
}
}
child = child->next;
}
/* print ending indent */
if (!buf_append((format && count) ? depth + 2 : 1)) return 0;
if (format && count)
{
buf_putc('\n');
for (i = 0; i < depth; i++) buf_putc('\t');
}
buf_putc(']');
return 1;
}
/**
* \brief render a object to text.
* \param[in] json: json handle
* \param[in] buf: buf handle
* \param[in] depth: print depth, indentation
* \param[in] format: 0 gives unformatted, otherwise gives formatted
* \return 1 success or 0 fail
*/
static int print_object(json_t json, BUFFER* buf, int depth, int format)
{
int i;
json_t child = json->value.child_;
/* empty object */
if (!child)
{
if (!buf_append(2)) return 0;
buf_putc('{');
buf_putc('}');
return 1;
}
if (!buf_append(format ? 2 : 1)) return 0;
buf_putc('{');
if (format) buf_putc('\n');
/* print children */
while (child)
{
/* print starting indent */
if (format)
{
if (!buf_append(depth + 1)) return 0;
for (i = 0; i < depth + 1; i++) buf_putc('\t');
}
/* print key */
if (!print_string_buffer(child->key, buf)) return 0;
/* print indicator ':' */
if (!buf_append(format ? 2 : 1)) return 0;
buf_putc(':');
if (format) buf_putc('\t');
/* print value */
if (!print_json(child, buf, depth + 1, format)) return 0;
/* print separator ',' */
if (!buf_append((child->next ? 1 : 0) + (format ? 1 : 0))) return 0;
if (child->next) buf_putc(',');
if (format) buf_putc('\n');
child = child->next;
}
/* print ending indent */
if (!buf_append(format ? depth + 1 : 1)) return 0;
if (format)
{
for (i = 0; i < depth; i++) buf_putc('\t');
}
buf_putc('}');
return 1;
}
/**
* \brief Print a json value to a buffer.
*
* \param[in] json The json value to be printed
* \param[in] buf The buffer to print the json value to
* \param[in] depth The current depth of the json value (used for pretty printing)
* \param[in] format The formatting option (compact or indented)
*
* \return Returns 1 if the printing is successful, 0 otherwise
*/
static int print_json(json_t json, BUFFER* buf, int depth, int format)
{
switch (json->type)
{
case JSON_TYPE_NULL:
{
/* Print "null" */
if (!buf_append(4)) return 0;
buf_putc('n');
buf_putc('u');
buf_putc('l');
buf_putc('l');
break;
}
case JSON_TYPE_BOOL:
{
if (json->value.bool_ == JSON_FALSE)
{
/* Print "false" */
if (!buf_append(5)) return 0;
buf_putc('f');
buf_putc('a');
buf_putc('l');
buf_putc('s');
buf_putc('e');
}
else
{
/* Print "true" */
if (!buf_append(4)) return 0;
buf_putc('t');
buf_putc('r');
buf_putc('u');
buf_putc('e');
}
break;
}
case JSON_TYPE_INT:
case JSON_TYPE_FLOAT: return print_number(json, buf);
case JSON_TYPE_STRING: return print_string(json, buf);
case JSON_TYPE_ARRAY: return print_array(json, buf, depth, format);
case JSON_TYPE_OBJECT: return print_object(json, buf, depth, format);
}
return 1;
}
/**
* \brief convert json to text, using a buffered strategy.
* \param[in] json: json handle
* \param[in] preset: preset is a guess at the final size, guessing well reduces reallocation
* \param[in] unformat: unformat=0 gives formatted, otherwise gives unformatted
* \param[out] len: address that receives the length of printed characters
* \return address of converted text, free the char* when finished
*/
char* json_dumps(json_t json, int preset, int unformat, int* len)
{
BUFFER p, *buf = &p;
if (!json) return NULL;
/* Allocate buffer and initialize */
if (preset < 1) preset = 1;
p.address = (char*)malloc(preset);
if (!p.address) return NULL;
p.size = preset;
p.end = 0;
/* Start printing json */
if (!print_json(json, &p, 0, !unformat))
{
free(p.address);
return NULL;
}
/* At the end of the text */
if (!buf_append(1))
{
free(p.address);
return NULL;
}
buf_end() = '\0';
/* Output conversion length */
if (len) *len = p.end;
return p.address;
}
/**
* \brief according to the json object, generate a file.
* \param[in] json: json handle
* \param[in] filename: file name
* \return file length or negative fail
*/
int json_file_dump(json_t json, char* filename)
{
FILE* f;
char* out;
int len;
if (!json) return -1;
/* Convert characters */
out = json_dumps(json, 0, 0, &len);
if (!out) return -1;
/* Open file */
f = fopen(filename, "w");
if (!f)
{
free(out);
return -1;
}
/* Write the string to the file */
fwrite(out, 1, len, f);
/* Close file */
fclose(f);
/* free the sapce of string */
free(out);
return len;
}
/**
* \brief json text parser.
* \param[in] text: address of text
* \return the address of the next meaningful character
*/
json_t json_loads(const char* text)
{
json_t json = NULL;
/* reset error info */
lbegin = text;
eline = 1;
etype = JSON_E_OK;
/* create json object and parse */
json = json_create();
if (!json)
{
E(JSON_E_MEMORY);
return NULL;
}
text = parse_text(json, skip(text));
/* parse failure. error is set. */
if (!text)
{
json_delete(json);
return NULL;
}
/* check whether there are meaningless characters after the text after parsing */
text = skip(text);
if (*text)
{
json_delete(json);
E(JSON_E_END);
return NULL;
}
return json;
}
/**
* \brief load a json file, parse and generate json objects.
* \param[in] filename: file name
* \return json handle or NULL fail
*/
json_t json_file_load(char* filename)
{
FILE* f;
json_t json = NULL;
long len;
char* data;
/* Open file */
f = fopen(filename, "rb");
if (!f) return NULL;
/* Get the length of file */
fseek(f, 0, SEEK_END);
len = ftell(f);
fseek(f, 0, SEEK_SET);
/* read file */
data = (char*)malloc(len + 1);
if (data)
{
/* Read the file content into `data` */
fread(data, 1, len, f);
fclose(f);
}
else
{
fclose(f);
return NULL;
}
/* Add string terminator */
data[len] = 0;
/* Load json string */
json = json_loads(data);
/* Free up space */
free(data);
return json;
}
/**
* \brief minify json text, remove the character that does not affect the analysis.
* \param[in] text: the address of the source text
* \return none
*/
void json_minify(char* text)
{
char* into = text;
while (*text)
{
/* whitespace characters. */
if (*text == ' ' || *text == '\t' || *text == '\r' || *text == '\n')
{
text++;
}
/* double-slash comments, to end of line. */
else if (*text == '/' && text[1] == '/')
{
while (*text && *text != '\n') text++;
}
/* multiline comments. */
else if (*text == '/' && text[1] == '*')
{
while (*text && !(*text == '*' && text[1] == '/')) text++;
text += 2;
}
/* string literals, which are \" sensitive. */
else if (*text == '\"')
{
*into++ = *text++;
while (*text && *text != '\"')
{
if (*text == '\\') *into++ = *text++;
*into++ = *text++;
}
*into++ = *text++;
}
/* all other characters. */
else *into++ = *text++;
}
*into = 0; /* and null-terminate. */
}