diff --git a/README.md b/README.md old mode 100644 new mode 100755 index 0f22a2e..7d1feb2 --- a/README.md +++ b/README.md @@ -1,6 +1,5 @@ ##About -Parson is a lighweight [json](http://json.org) parser and reader written in C. -If you need serialization and json value building in code, use [parson-devel](https://github.com/kgabis/parson-devel), which has extended API, but may change in future. +Parson is a lighweight [json](http://json.org) library written in C. ##Features * Full JSON support @@ -11,7 +10,7 @@ If you need serialization and json value building in code, use [parson-devel](ht * Test suites ##Installation -Run the following code: +Run: ``` git clone https://github.com/kgabis/parson.git ``` @@ -19,8 +18,9 @@ and copy parson.h and parson.c to you source code tree. Run ```make test``` to compile and run tests. -##Example -Here is a function, which prints basic commit info (date, sha and author) from a github repository. It's also included in tests.c file, you can just uncomment and run it. +##Examples +###Parsing JSON +Here is a function, which prints basic commit info (date, sha and author) from a github repository. ```c void print_commits_info(const char *username, const char *repo) { JSON_Value *root_value; @@ -75,5 +75,71 @@ Date SHA Author ... ``` +###Persistence +In this example I'm using parson to save user information to a file and then load it and validate later. +```c +void persistence_example(void) { + JSON_Value *schema = json_parse_string("{\"name\":\"\"}"); + JSON_Value *user_data = json_parse_file("user_data.json"); + char buf[256]; + const char *name = NULL; + if (!user_data || !json_validate(schema, user_data) == JSONSuccess) { + puts("Enter your name:"); + scanf("%s", buf); + user_data = json_value_init_object(); + json_object_set_string(json_object(user_data), "name", buf); + json_serialize_to_file(user_data, "user_data.json"); + } + name = json_object_get_string(json_object(user_data), "name"); + printf("Hello, %s.", name); + json_value_free(schema); + json_value_free(user_data); + return; +} +``` + +###Serialization +Creating JSON values is very simple thanks to the dot notation. +Object hierarchy is automatically created when addressing specific fields. +In the following example I create a simple JSON value containing basic information about a person. +```c +void serialization_example(void) { + JSON_Value *root_value = json_value_init_object(); + JSON_Object *root_object = json_value_get_object(root_value); + char *serialized_string = NULL; + json_object_set_string(root_object, "name", "John Smith"); + json_object_set_number(root_object, "age", 25); + json_object_dotset_string(root_object, "address.city", "Cupertino"); + json_object_dotset_value(root_object, "contact.emails", json_parse_string("[\"email@example.com\",\"email2@example.com\"]")); + serialized_string = json_serialize_to_string(root_value); + puts(serialized_string); + json_free_serialized_string(serialized_string); +} + +``` + +Created value (after formatting outside parson): +``` +{ + "name":"John Smith", + "age":25, + "address":{ + "city":"Cupertino" + }, + "contact":{ + "emails":[ + "email@example.com", + "email2@example.com" + ] + } +} +``` + +##Contributing + +I will always merge *working* bug fixes. However, if you want to add something to the API, +I *won't* merge it without prior discussion. +Remember to follow parson's code style and write appropriate tests. + ##License [The MIT License (MIT)](http://opensource.org/licenses/mit-license.php) \ No newline at end of file diff --git a/parson.c b/parson.c old mode 100644 new mode 100755 index 319af19..163a04e --- a/parson.c +++ b/parson.c @@ -20,6 +20,9 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +#ifdef _MSC_VER +#define _CRT_SECURE_NO_WARNINGS +#endif #include "parson.h" @@ -27,21 +30,25 @@ #include #include #include +#include -#define ERROR 0 -#define SUCCESS 1 #define STARTING_CAPACITY 15 #define ARRAY_MAX_CAPACITY 122880 /* 15*(2^13) */ #define OBJECT_MAX_CAPACITY 960 /* 15*(2^6) */ #define MAX_NESTING 19 -#define sizeof_token(a) (sizeof(a) - 1) -#define skip_char(str) ((*str)++) -#define skip_whitespaces(str) while (isspace(**str)) { skip_char(str); } +#define DOUBLE_SERIALIZATION_FORMAT "%f" + +#define SIZEOF_TOKEN(a) (sizeof(a) - 1) +#define SKIP_CHAR(str) ((*str)++) +#define SKIP_WHITESPACES(str) while (isspace(**str)) { SKIP_CHAR(str); } #define MAX(a, b) ((a) > (b) ? (a) : (b)) -#define parson_malloc(a) malloc(a) -#define parson_free(a) free((void*)a) -#define parson_realloc(a, b) realloc(a, b) +#define PARSON_MALLOC(a) malloc(a) +#define PARSON_FREE(a) free((void*)(a)) +#define PARSON_REALLOC(a, b) realloc((a), (b)) + +#define PRINT_AND_SKIP(str, to_append) str += sprintf(str, to_append); +#define PRINTF_AND_SKIP(str, format, to_append) str += sprintf(str, format, to_append); /* Type definitions */ typedef union json_value_value { @@ -76,34 +83,32 @@ static char * read_file(const char *filename); static void remove_comments(char *string, const char *start_token, const char *end_token); static int try_realloc(void **ptr, size_t new_size); static char * parson_strndup(const char *string, size_t n); +static char * parson_strdup(const char *string); static int is_utf(const unsigned char *string); static int is_decimal(const char *string, size_t length); +static size_t parson_strlen(const char *string); /* JSON Object */ static JSON_Object * json_object_init(void); -static int json_object_add(JSON_Object *object, const char *name, JSON_Value *value); -static int json_object_resize(JSON_Object *object, size_t capacity); +static JSON_Status json_object_add(JSON_Object *object, const char *name, JSON_Value *value); +static JSON_Status json_object_resize(JSON_Object *object, size_t capacity); static JSON_Value * json_object_nget_value(const JSON_Object *object, const char *name, size_t n); static void json_object_free(JSON_Object *object); /* JSON Array */ static JSON_Array * json_array_init(void); -static int json_array_add(JSON_Array *array, JSON_Value *value); -static int json_array_resize(JSON_Array *array, size_t capacity); +static JSON_Status json_array_add(JSON_Array *array, JSON_Value *value); +static JSON_Status json_array_resize(JSON_Array *array, size_t capacity); static void json_array_free(JSON_Array *array); /* JSON Value */ -static JSON_Value * json_value_init_object(void); -static JSON_Value * json_value_init_array(void); -static JSON_Value * json_value_init_string(const char *string); -static JSON_Value * json_value_init_number(double number); -static JSON_Value * json_value_init_boolean(int boolean); -static JSON_Value * json_value_init_null(void); +static JSON_Value * json_value_init_string_no_copy(const char *string); /* Parser */ static void skip_quotes(const char **string); -static int parse_utf_16(char **processed, char **unprocessed); -static const char * get_processed_string(const char **string); +static int parse_utf_16(const char **unprocessed, char **processed); +static char* process_string(const char *input, size_t len); +static const char * get_quoted_string(const char **string); static JSON_Value * parse_object_value(const char **string, size_t nesting); static JSON_Value * parse_array_value(const char **string, size_t nesting); static JSON_Value * parse_string_value(const char **string); @@ -112,17 +117,27 @@ static JSON_Value * parse_number_value(const char **string); static JSON_Value * parse_null_value(const char **string); static JSON_Value * parse_value(const char **string, size_t nesting); +/* Serialization */ +static size_t json_serialization_size_r(const JSON_Value *value, char *buf); +static char * json_serialize_to_buffer_r(const JSON_Value *value, char *buf); +static char * json_serialize_string(const char *string, char *buf); + /* Various */ static int try_realloc(void **ptr, size_t new_size) { - void *reallocated_ptr = parson_realloc(*ptr, new_size); - if (!reallocated_ptr) - return ERROR; + void *reallocated_ptr = NULL; + if (new_size == 0) { + return JSONFailure; + } + reallocated_ptr = PARSON_REALLOC(*ptr, new_size); + if (reallocated_ptr == NULL) { + return JSONFailure; + } *ptr = reallocated_ptr; - return SUCCESS; + return JSONSuccess; } static char * parson_strndup(const char *string, size_t n) { - char *output_string = (char*)parson_malloc(n + 1); + char *output_string = (char*)PARSON_MALLOC(n + 1); if (!output_string) return NULL; output_string[n] = '\0'; @@ -130,6 +145,10 @@ static char * parson_strndup(const char *string, size_t n) { return output_string; } +static char * parson_strdup(const char *string) { + return parson_strndup(string, strlen(string)); +} + static int is_utf(const unsigned char *s) { return isxdigit(s[0]) && isxdigit(s[1]) && isxdigit(s[2]) && isxdigit(s[3]); } @@ -145,6 +164,18 @@ static int is_decimal(const char *string, size_t length) { return 1; } +static size_t parson_strlen(const char *string) { + size_t result = 0; + size_t i = 0, len = strlen(string); + for (i = 0; i < len; i++) { + if (strchr("\"\\/\b\f\n\r\t", string[i])) /* must be escaped */ + result += 2; + else + result += 1; + } + return result; +} + static char * read_file(const char * filename) { FILE *fp = fopen(filename, "r"); size_t file_size; @@ -154,7 +185,7 @@ static char * read_file(const char * filename) { fseek(fp, 0L, SEEK_END); file_size = ftell(fp); rewind(fp); - file_contents = (char*)parson_malloc(sizeof(char) * (file_size + 1)); + file_contents = (char*)PARSON_MALLOC(sizeof(char) * (file_size + 1)); if (!file_contents) { fclose(fp); return NULL; @@ -162,7 +193,7 @@ static char * read_file(const char * filename) { if (fread(file_contents, file_size, 1, fp) < 1) { if (ferror(fp)) { fclose(fp); - parson_free(file_contents); + PARSON_FREE(file_contents); return NULL; } } @@ -204,7 +235,7 @@ static void remove_comments(char *string, const char *start_token, const char *e /* JSON Object */ static JSON_Object * json_object_init(void) { - JSON_Object *new_obj = (JSON_Object*)parson_malloc(sizeof(JSON_Object)); + JSON_Object *new_obj = (JSON_Object*)PARSON_MALLOC(sizeof(JSON_Object)); if (!new_obj) return NULL; new_obj->names = (const char**)NULL; @@ -214,33 +245,33 @@ static JSON_Object * json_object_init(void) { return new_obj; } -static int json_object_add(JSON_Object *object, const char *name, JSON_Value *value) { +static JSON_Status json_object_add(JSON_Object *object, const char *name, JSON_Value *value) { size_t index; if (object->count >= object->capacity) { size_t new_capacity = MAX(object->capacity * 2, STARTING_CAPACITY); if (new_capacity > OBJECT_MAX_CAPACITY) - return ERROR; - if (json_object_resize(object, new_capacity) == ERROR) - return ERROR; + return JSONFailure; + if (json_object_resize(object, new_capacity) == JSONFailure) + return JSONFailure; } if (json_object_get_value(object, name) != NULL) - return ERROR; + return JSONFailure; index = object->count; - object->names[index] = parson_strndup(name, strlen(name)); + object->names[index] = parson_strdup(name); if (!object->names[index]) - return ERROR; + return JSONFailure; object->values[index] = value; object->count++; - return SUCCESS; + return JSONSuccess; } -static int json_object_resize(JSON_Object *object, size_t capacity) { - if (try_realloc((void**)&object->names, capacity * sizeof(char*)) == ERROR) - return ERROR; - if (try_realloc((void**)&object->values, capacity * sizeof(JSON_Value*)) == ERROR) - return ERROR; +static JSON_Status json_object_resize(JSON_Object *object, size_t capacity) { + if (try_realloc((void**)&object->names, capacity * sizeof(char*)) == JSONFailure) + return JSONFailure; + if (try_realloc((void**)&object->values, capacity * sizeof(JSON_Value*)) == JSONFailure) + return JSONFailure; object->capacity = capacity; - return SUCCESS; + return JSONSuccess; } static JSON_Value * json_object_nget_value(const JSON_Object *object, const char *name, size_t n) { @@ -257,17 +288,17 @@ static JSON_Value * json_object_nget_value(const JSON_Object *object, const char static void json_object_free(JSON_Object *object) { while(object->count--) { - parson_free(object->names[object->count]); + PARSON_FREE(object->names[object->count]); json_value_free(object->values[object->count]); } - parson_free(object->names); - parson_free(object->values); - parson_free(object); + PARSON_FREE(object->names); + PARSON_FREE(object->values); + PARSON_FREE(object); } /* JSON Array */ static JSON_Array * json_array_init(void) { - JSON_Array *new_array = (JSON_Array*)parson_malloc(sizeof(JSON_Array)); + JSON_Array *new_array = (JSON_Array*)PARSON_MALLOC(sizeof(JSON_Array)); if (!new_array) return NULL; new_array->items = (JSON_Value**)NULL; @@ -276,62 +307,36 @@ static JSON_Array * json_array_init(void) { return new_array; } -static int json_array_add(JSON_Array *array, JSON_Value *value) { +static JSON_Status json_array_add(JSON_Array *array, JSON_Value *value) { if (array->count >= array->capacity) { size_t new_capacity = MAX(array->capacity * 2, STARTING_CAPACITY); if (new_capacity > ARRAY_MAX_CAPACITY) - return ERROR; - if (!json_array_resize(array, new_capacity)) - return ERROR; + return JSONFailure; + if (json_array_resize(array, new_capacity) == JSONFailure) + return JSONFailure; } array->items[array->count] = value; array->count++; - return SUCCESS; + return JSONSuccess; } -static int json_array_resize(JSON_Array *array, size_t capacity) { - if (try_realloc((void**)&array->items, capacity * sizeof(JSON_Value*)) == ERROR) - return ERROR; +static JSON_Status json_array_resize(JSON_Array *array, size_t capacity) { + if (try_realloc((void**)&array->items, capacity * sizeof(JSON_Value*)) == JSONFailure) + return JSONFailure; array->capacity = capacity; - return SUCCESS; + return JSONSuccess; } static void json_array_free(JSON_Array *array) { while (array->count--) json_value_free(array->items[array->count]); - parson_free(array->items); - parson_free(array); + PARSON_FREE(array->items); + PARSON_FREE(array); } /* JSON Value */ -static JSON_Value * json_value_init_object(void) { - JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value)); - if (!new_value) - return NULL; - new_value->type = JSONObject; - new_value->value.object = json_object_init(); - if (!new_value->value.object) { - parson_free(new_value); - return NULL; - } - return new_value; -} - -static JSON_Value * json_value_init_array(void) { - JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value)); - if (!new_value) - return NULL; - new_value->type = JSONArray; - new_value->value.array = json_array_init(); - if (!new_value->value.array) { - parson_free(new_value); - return NULL; - } - return new_value; -} - -static JSON_Value * json_value_init_string(const char *string) { - JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value)); +static JSON_Value * json_value_init_string_no_copy(const char *string) { + JSON_Value *new_value = (JSON_Value*)PARSON_MALLOC(sizeof(JSON_Value)); if (!new_value) return NULL; new_value->type = JSONString; @@ -339,55 +344,29 @@ static JSON_Value * json_value_init_string(const char *string) { return new_value; } -static JSON_Value * json_value_init_number(double number) { - JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value)); - if (!new_value) - return NULL; - new_value->type = JSONNumber; - new_value->value.number = number; - return new_value; -} - -static JSON_Value * json_value_init_boolean(int boolean) { - JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value)); - if (!new_value) - return NULL; - new_value->type = JSONBoolean; - new_value->value.boolean = boolean; - return new_value; -} - -static JSON_Value * json_value_init_null(void) { - JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value)); - if (!new_value) - return NULL; - new_value->type = JSONNull; - return new_value; -} - /* Parser */ static void skip_quotes(const char **string) { - skip_char(string); + SKIP_CHAR(string); while (**string != '\"') { if (**string == '\0') return; if (**string == '\\') { - skip_char(string); + SKIP_CHAR(string); if (**string == '\0') return; } - skip_char(string); + SKIP_CHAR(string); } - skip_char(string); + SKIP_CHAR(string); } -static int parse_utf_16(char **processed, char **unprocessed) { +static int parse_utf_16(const char **unprocessed, char **processed) { unsigned int cp, lead, trail; char *processed_ptr = *processed; - char *unprocessed_ptr = *unprocessed; + const char *unprocessed_ptr = *unprocessed; unprocessed_ptr++; /* skips u */ if (!is_utf((const unsigned char*)unprocessed_ptr) || sscanf(unprocessed_ptr, "%4x", &cp) == EOF) - return ERROR; + return JSONFailure; if (cp < 0x80) { *processed_ptr = cp; /* 0xxxxxxx */ } else if (cp < 0x800) { @@ -404,7 +383,7 @@ static int parse_utf_16(char **processed, char **unprocessed) { !is_utf((const unsigned char*)unprocessed_ptr) || sscanf(unprocessed_ptr, "%4x", &trail) == EOF || trail < 0xDC00 || trail > 0xDFFF) { /* valid trail surrogate? (0xDC00..0xDFFF) */ - return ERROR; + return JSONFailure; } cp = ((((lead-0xD800)&0x3FF)<<10)|((trail-0xDC00)&0x3FF))+0x010000; *processed_ptr++ = (((cp >> 18) & 0x07) | 0xF0); /* 11110xxx */ @@ -412,69 +391,73 @@ static int parse_utf_16(char **processed, char **unprocessed) { *processed_ptr++ = (((cp >> 6) & 0x3F) | 0x80); /* 10xxxxxx */ *processed_ptr = (((cp ) & 0x3F) | 0x80); /* 10xxxxxx */ } else { /* trail surrogate before lead surrogate */ - return ERROR; + return JSONFailure; } unprocessed_ptr += 3; *processed = processed_ptr; *unprocessed = unprocessed_ptr; - return SUCCESS; + return JSONSuccess; } -/* Returns contents of a string inside double quotes and parses escaped - characters inside. - Example: "\u006Corem ipsum" -> lorem ipsum */ -static const char * get_processed_string(const char **string) { - const char *string_start = *string; - char *output = NULL, *processed_ptr = NULL, *unprocessed_ptr = NULL; - skip_quotes(string); - if (**string == '\0') - return NULL; - output = parson_strndup(string_start + 1, *string - string_start - 2); - if (!output) - return NULL; - processed_ptr = unprocessed_ptr = output; - while (*unprocessed_ptr != '\0') { - if (*unprocessed_ptr == '\\') { - unprocessed_ptr++; - switch (*unprocessed_ptr) { - case '\"': *processed_ptr = '\"'; break; - case '\\': *processed_ptr = '\\'; break; - case '/': *processed_ptr = '/'; break; - case 'b': *processed_ptr = '\b'; break; - case 'f': *processed_ptr = '\f'; break; - case 'n': *processed_ptr = '\n'; break; - case 'r': *processed_ptr = '\r'; break; - case 't': *processed_ptr = '\t'; break; + +/* Copies and processes passed string up to supplied length. +Example: "\u006Corem ipsum" -> lorem ipsum */ +static char* process_string(const char *input, size_t len) { + const char *input_ptr = input; + char *output = (char*)PARSON_MALLOC((len + 1) * sizeof(char)); + char *output_ptr = output; + while ((*input_ptr != '\0') && (size_t)(input_ptr - input) < len) { + if (*input_ptr == '\\') { + input_ptr++; + switch (*input_ptr) { + case '\"': *output_ptr = '\"'; break; + case '\\': *output_ptr = '\\'; break; + case '/': *output_ptr = '/'; break; + case 'b': *output_ptr = '\b'; break; + case 'f': *output_ptr = '\f'; break; + case 'n': *output_ptr = '\n'; break; + case 'r': *output_ptr = '\r'; break; + case 't': *output_ptr = '\t'; break; case 'u': - if (parse_utf_16(&processed_ptr, &unprocessed_ptr) == ERROR) { - parson_free(output); - return NULL; - } + if (parse_utf_16(&input_ptr, &output_ptr) == JSONFailure) + goto error; break; default: - parson_free(output); - return NULL; - break; + goto error; } - } else if ((unsigned char)*unprocessed_ptr < 0x20) { - parson_free(output); /* 0x00-0x19 are invalid characters for json string (http://www.ietf.org/rfc/rfc4627.txt) */ - return NULL; + } else if ((unsigned char)*input_ptr < 0x20) { + goto error; /* 0x00-0x19 are invalid characters for json string (http://www.ietf.org/rfc/rfc4627.txt) */ } else { - *processed_ptr = *unprocessed_ptr; + *output_ptr = *input_ptr; } - processed_ptr++; - unprocessed_ptr++; + output_ptr++; + input_ptr++; } - *processed_ptr = '\0'; - if (try_realloc((void**)&output, strlen(output) + 1) == ERROR) - return NULL; + *output_ptr = '\0'; + if (try_realloc((void**)&output, strlen(output) + 1) == JSONFailure) + goto error; return output; +error: + free(output); + return NULL; +} + +/* Return processed contents of a string between quotes and + skips passed argument to a matching quote. */ +static const char * get_quoted_string(const char **string) { + const char *string_start = *string; + size_t string_len = 0; + skip_quotes(string); + if (**string == '\0') + return NULL; + string_len = *string - string_start - 2; /* length without quotes */ + return process_string(string_start + 1, string_len); } static JSON_Value * parse_value(const char **string, size_t nesting) { if (nesting > MAX_NESTING) return NULL; - skip_whitespaces(string); + SKIP_WHITESPACES(string); switch (**string) { case '{': return parse_object_value(string, nesting + 1); @@ -499,48 +482,48 @@ static JSON_Value * parse_object_value(const char **string, size_t nesting) { JSON_Value *output_value = json_value_init_object(), *new_value = NULL; JSON_Object *output_object = json_value_get_object(output_value); const char *new_key = NULL; - if (!output_value) + if (output_value == NULL) return NULL; - skip_char(string); - skip_whitespaces(string); + SKIP_CHAR(string); + SKIP_WHITESPACES(string); if (**string == '}') { /* empty object */ - skip_char(string); + SKIP_CHAR(string); return output_value; } while (**string != '\0') { - new_key = get_processed_string(string); - skip_whitespaces(string); - if (!new_key || **string != ':') { + new_key = get_quoted_string(string); + SKIP_WHITESPACES(string); + if (new_key == NULL || **string != ':') { json_value_free(output_value); return NULL; } - skip_char(string); + SKIP_CHAR(string); new_value = parse_value(string, nesting); - if (!new_value) { - parson_free(new_key); + if (new_value == NULL) { + PARSON_FREE(new_key); json_value_free(output_value); return NULL; } - if(!json_object_add(output_object, new_key, new_value)) { - parson_free(new_key); - parson_free(new_value); + if(json_object_add(output_object, new_key, new_value) == JSONFailure) { + PARSON_FREE(new_key); + PARSON_FREE(new_value); json_value_free(output_value); return NULL; } - parson_free(new_key); - skip_whitespaces(string); + PARSON_FREE(new_key); + SKIP_WHITESPACES(string); if (**string != ',') break; - skip_char(string); - skip_whitespaces(string); + SKIP_CHAR(string); + SKIP_WHITESPACES(string); } - skip_whitespaces(string); + SKIP_WHITESPACES(string); if (**string != '}' || /* Trim object after parsing is over */ - json_object_resize(output_object, json_object_get_count(output_object)) == ERROR) { - json_value_free(output_value); - return NULL; + json_object_resize(output_object, json_object_get_count(output_object)) == JSONFailure) { + json_value_free(output_value); + return NULL; } - skip_char(string); + SKIP_CHAR(string); return output_value; } @@ -549,10 +532,10 @@ static JSON_Value * parse_array_value(const char **string, size_t nesting) { JSON_Array *output_array = json_value_get_array(output_value); if (!output_value) return NULL; - skip_char(string); - skip_whitespaces(string); + SKIP_CHAR(string); + SKIP_WHITESPACES(string); if (**string == ']') { /* empty array */ - skip_char(string); + SKIP_CHAR(string); return output_value; } while (**string != '\0') { @@ -561,37 +544,37 @@ static JSON_Value * parse_array_value(const char **string, size_t nesting) { json_value_free(output_value); return NULL; } - if(json_array_add(output_array, new_array_value) == ERROR) { - parson_free(new_array_value); + if(json_array_add(output_array, new_array_value) == JSONFailure) { + PARSON_FREE(new_array_value); json_value_free(output_value); return NULL; } - skip_whitespaces(string); + SKIP_WHITESPACES(string); if (**string != ',') break; - skip_char(string); - skip_whitespaces(string); + SKIP_CHAR(string); + SKIP_WHITESPACES(string); } - skip_whitespaces(string); + SKIP_WHITESPACES(string); if (**string != ']' || /* Trim array after parsing is over */ - json_array_resize(output_array, json_array_get_count(output_array)) == ERROR) { - json_value_free(output_value); - return NULL; + json_array_resize(output_array, json_array_get_count(output_array)) == JSONFailure) { + json_value_free(output_value); + return NULL; } - skip_char(string); + SKIP_CHAR(string); return output_value; } static JSON_Value * parse_string_value(const char **string) { - const char *new_string = get_processed_string(string); + const char *new_string = get_quoted_string(string); if (!new_string) return NULL; - return json_value_init_string(new_string); + return json_value_init_string_no_copy(new_string); } static JSON_Value * parse_boolean_value(const char **string) { - size_t true_token_size = sizeof_token("true"); - size_t false_token_size = sizeof_token("false"); + size_t true_token_size = SIZEOF_TOKEN("true"); + size_t false_token_size = SIZEOF_TOKEN("false"); if (strncmp("true", *string, true_token_size) == 0) { *string += true_token_size; return json_value_init_boolean(1); @@ -616,7 +599,7 @@ static JSON_Value * parse_number_value(const char **string) { } static JSON_Value * parse_null_value(const char **string) { - size_t token_size = sizeof_token("null"); + size_t token_size = SIZEOF_TOKEN("null"); if (strncmp("null", *string, token_size) == 0) { *string += token_size; return json_value_init_null(); @@ -624,6 +607,147 @@ static JSON_Value * parse_null_value(const char **string) { return NULL; } +/* Serialization */ +static size_t json_serialization_size_r(const JSON_Value *value, char *buf) { + size_t result_size = 0; + const char *key = NULL; + JSON_Value *temp_value = NULL; + JSON_Array *array = NULL; + JSON_Object *object = NULL; + size_t i = 0, count = 0; + double num = 0.0; + switch (json_value_get_type(value)) { + case JSONArray: + array = json_value_get_array(value); + count = json_array_get_count(array); + result_size += 2; /* [ and ] brackets */ + if (count > 0) + result_size += count - 1; /* , between items */ + for (i = 0; i < count; i++) { + temp_value = json_array_get_value(array, i); + result_size += json_serialization_size_r(temp_value, buf); + } + return result_size; + case JSONObject: + object = json_value_get_object(value); + count = json_object_get_count(object); + result_size += 2; /* { and } brackets */ + if (count > 0) + result_size += (count * 2) - 1; /* : between keys and values and , between items */ + for (i = 0; i < count; i++) { + key = json_object_get_name(object, i); + result_size += parson_strlen(key) + 2; /* string and quotes */ + result_size += json_serialization_size_r(json_object_get_value(object, key), buf); + } + return result_size; + case JSONString: + return parson_strlen(json_value_get_string(value)) + 2; /* string and quotes */ + case JSONBoolean: + if (json_value_get_boolean(value)) + return 4; /* strlen("true"); */ + else + return 5; /* strlen("false"); */ + case JSONNumber: + num = json_value_get_number(value); + if (num == ((double)(int)num) ) /* check if num is integer */ + return (size_t)sprintf(buf, "%d", (int)num); + return (size_t)sprintf(buf, DOUBLE_SERIALIZATION_FORMAT, num); + case JSONNull: + return 4; /* strlen("null"); */ + case JSONError: + return 0; + default: + return 0; + } +} + +char* json_serialize_to_buffer_r(const JSON_Value *value, char *buf) +{ + const char *key = NULL, *string = NULL; + JSON_Value *temp_value = NULL; + JSON_Array *array = NULL; + JSON_Object *object = NULL; + size_t i = 0, count = 0; + double num = 0.0; + switch (json_value_get_type(value)) { + case JSONArray: + array = json_value_get_array(value); + count = json_array_get_count(array); + PRINT_AND_SKIP(buf, "["); + for (i = 0; i < count; i++) { + temp_value = json_array_get_value(array, i); + buf = json_serialize_to_buffer_r(temp_value, buf); + if (i < (count - 1)) + PRINT_AND_SKIP(buf, ","); + } + PRINT_AND_SKIP(buf, "]"); + return buf; + case JSONObject: + object = json_value_get_object(value); + count = json_object_get_count(object); + PRINT_AND_SKIP(buf, "{"); + for (i = 0; i < count; i++) { + key = json_object_get_name(object, i); + buf = json_serialize_string(key, buf); + PRINT_AND_SKIP(buf, ":"); + temp_value = json_object_get_value(object, key); + buf = json_serialize_to_buffer_r(temp_value, buf); + if (i < (count - 1)) + PRINT_AND_SKIP(buf, ","); + } + PRINT_AND_SKIP(buf, "}"); + return buf; + case JSONString: + string = json_value_get_string(value); + buf = json_serialize_string(string, buf); + return buf; + case JSONBoolean: + if (json_value_get_boolean(value)) { + PRINT_AND_SKIP(buf, "true"); + } else { + PRINT_AND_SKIP(buf, "false"); + } + return buf; + case JSONNumber: + num = json_value_get_number(value); + if (num == ((double)(int)num)) { /* check if num is integer */ + PRINTF_AND_SKIP(buf, "%d", (int)num); + } else { + PRINTF_AND_SKIP(buf, DOUBLE_SERIALIZATION_FORMAT, num); + } + return buf; + case JSONNull: + PRINT_AND_SKIP(buf, "null"); + return buf; + case JSONError: + return NULL; + default: + return NULL; + } +} + +static char * json_serialize_string(const char *string, char *buf) { + size_t i = 0, len = strlen(string); + char c = '\0'; + PRINT_AND_SKIP(buf, "\"") + for (i = 0; i < len; i++) { + c = string[i]; + switch (c) { + case '\"': PRINT_AND_SKIP(buf, "\\\""); break; + case '\\': PRINT_AND_SKIP(buf, "\\\\"); break; + case '/': PRINT_AND_SKIP(buf, "\\/"); break; + case '\b': PRINT_AND_SKIP(buf, "\\b"); break; + case '\f': PRINT_AND_SKIP(buf, "\\f"); break; + case '\n': PRINT_AND_SKIP(buf, "\\n"); break; + case '\r': PRINT_AND_SKIP(buf, "\\r"); break; + case '\t': PRINT_AND_SKIP(buf, "\\t"); break; + default: PRINTF_AND_SKIP(buf, "%c", c); break; + } + } + PRINT_AND_SKIP(buf, "\""); + return buf; +} + /* Parser API */ JSON_Value * json_parse_file(const char *filename) { char *file_contents = read_file(filename); @@ -631,7 +755,7 @@ JSON_Value * json_parse_file(const char *filename) { if (!file_contents) return NULL; output_value = json_parse_string(file_contents); - parson_free(file_contents); + PARSON_FREE(file_contents); return output_value; } @@ -641,14 +765,14 @@ JSON_Value * json_parse_file_with_comments(const char *filename) { if (!file_contents) return NULL; output_value = json_parse_string_with_comments(file_contents); - parson_free(file_contents); + PARSON_FREE(file_contents); return output_value; } JSON_Value * json_parse_string(const char *string) { if (!string) return NULL; - skip_whitespaces(&string); + SKIP_WHITESPACES(&string); if (*string != '{' && *string != '[') return NULL; return parse_value((const char**)&string, 0); @@ -657,19 +781,19 @@ JSON_Value * json_parse_string(const char *string) { JSON_Value * json_parse_string_with_comments(const char *string) { JSON_Value *result = NULL; char *string_mutable_copy = NULL, *string_mutable_copy_ptr = NULL; - string_mutable_copy = parson_strndup(string, strlen(string)); + string_mutable_copy = parson_strdup(string); if (!string_mutable_copy) return NULL; remove_comments(string_mutable_copy, "/*", "*/"); remove_comments(string_mutable_copy, "//", "\n"); string_mutable_copy_ptr = string_mutable_copy; - skip_whitespaces(&string_mutable_copy_ptr); + SKIP_WHITESPACES(&string_mutable_copy_ptr); if (*string_mutable_copy_ptr != '{' && *string_mutable_copy_ptr != '[') { - parson_free(string_mutable_copy); + PARSON_FREE(string_mutable_copy); return NULL; } result = parse_value((const char**)&string_mutable_copy_ptr, 0); - parson_free(string_mutable_copy); + PARSON_FREE(string_mutable_copy); return result; } @@ -800,7 +924,7 @@ void json_value_free(JSON_Value *value) { json_object_free(value->value.object); break; case JSONString: - if (value->value.string) { parson_free(value->value.string); } + if (value->value.string) { PARSON_FREE(value->value.string); } break; case JSONArray: json_array_free(value->value.array); @@ -808,5 +932,531 @@ void json_value_free(JSON_Value *value) { default: break; } - parson_free(value); + PARSON_FREE(value); +} + +JSON_Value * json_value_init_object(void) { + JSON_Value *new_value = (JSON_Value*)PARSON_MALLOC(sizeof(JSON_Value)); + if (!new_value) + return NULL; + new_value->type = JSONObject; + new_value->value.object = json_object_init(); + if (!new_value->value.object) { + PARSON_FREE(new_value); + return NULL; + } + return new_value; +} + +JSON_Value * json_value_init_array(void) { + JSON_Value *new_value = (JSON_Value*)PARSON_MALLOC(sizeof(JSON_Value)); + if (!new_value) + return NULL; + new_value->type = JSONArray; + new_value->value.array = json_array_init(); + if (!new_value->value.array) { + PARSON_FREE(new_value); + return NULL; + } + return new_value; +} + +JSON_Value * json_value_init_string(const char *string) { + char *processed_copy = process_string(string, strlen(string)); + if (processed_copy == NULL) + return NULL; + return json_value_init_string_no_copy(processed_copy); +} + +JSON_Value * json_value_init_number(double number) { + JSON_Value *new_value = (JSON_Value*)PARSON_MALLOC(sizeof(JSON_Value)); + if (!new_value) + return NULL; + new_value->type = JSONNumber; + new_value->value.number = number; + return new_value; +} + +JSON_Value * json_value_init_boolean(int boolean) { + JSON_Value *new_value = (JSON_Value*)PARSON_MALLOC(sizeof(JSON_Value)); + if (!new_value) + return NULL; + new_value->type = JSONBoolean; + new_value->value.boolean = boolean ? 1 : 0; + return new_value; +} + +JSON_Value * json_value_init_null(void) { + JSON_Value *new_value = (JSON_Value*)PARSON_MALLOC(sizeof(JSON_Value)); + if (!new_value) + return NULL; + new_value->type = JSONNull; + return new_value; +} + +JSON_Value * json_value_deep_copy(const JSON_Value *value) { + size_t i = 0; + JSON_Value *return_value = NULL, *temp_value_copy = NULL, *temp_value = NULL; + const char *temp_string = NULL, *temp_string_copy = NULL, *temp_key = NULL; + JSON_Array *temp_array = NULL, *temp_array_copy = NULL; + JSON_Object *temp_object = NULL, *temp_object_copy = NULL; + + switch (json_value_get_type(value)) { + case JSONArray: + temp_array = json_value_get_array(value); + return_value = json_value_init_array(); + if (return_value == NULL) + return NULL; + temp_array_copy = json_value_get_array(return_value); + for (i = 0; i < json_array_get_count(temp_array); i++) { + temp_value = json_array_get_value(temp_array, i); + temp_value_copy = json_value_deep_copy(temp_value); + if (temp_value_copy == NULL) { + json_value_free(return_value); + return NULL; + } + if (json_array_add(temp_array_copy, temp_value_copy) == JSONFailure) { + json_value_free(return_value); + json_value_free(temp_value_copy); + return NULL; + } + } + return return_value; + case JSONObject: + temp_object = json_value_get_object(value); + return_value = json_value_init_object(); + if (return_value == NULL) + return NULL; + temp_object_copy = json_value_get_object(return_value); + for (i = 0; i < json_object_get_count(temp_object); i++) { + temp_key = json_object_get_name(temp_object, i); + temp_value = json_object_get_value(temp_object, temp_key); + temp_value_copy = json_value_deep_copy(temp_value); + if (temp_value_copy == NULL) { + json_value_free(return_value); + return NULL; + } + if (json_object_add(temp_object_copy, temp_key, temp_value_copy) == JSONFailure) { + json_value_free(return_value); + json_value_free(temp_value_copy); + return NULL; + } + } + return return_value; + case JSONBoolean: + return json_value_init_boolean(json_value_get_boolean(value)); + case JSONNumber: + return json_value_init_number(json_value_get_number(value)); + case JSONString: + temp_string = json_value_get_string(value); + temp_string_copy = parson_strdup(temp_string); + if (temp_string_copy == NULL) + return NULL; + return json_value_init_string_no_copy(temp_string_copy); + case JSONNull: + return json_value_init_null(); + case JSONError: + return NULL; + default: + return NULL; + } +} + +size_t json_serialization_size(const JSON_Value *value) { + char buf[1100]; /* recursively allocating buffer on stack is a bad idea, so let's do it only once */ + return json_serialization_size_r(value, buf) + 1; +} + +JSON_Status json_serialize_to_buffer(const JSON_Value *value, char *buf, size_t buf_size_in_bytes) { + char *serialization_result = NULL; + size_t needed_size_in_bytes = json_serialization_size(value); + if (buf_size_in_bytes < needed_size_in_bytes) { + return JSONFailure; + } + serialization_result = json_serialize_to_buffer_r(value, buf); + if(serialization_result == NULL) + return JSONFailure; + return JSONSuccess; +} + +JSON_Status json_serialize_to_file(const JSON_Value *value, const char *filename) { + JSON_Status return_code = JSONSuccess; + FILE *fp = NULL; + char *serialized_string = json_serialize_to_string(value); + if (serialized_string == NULL) { + return JSONFailure; + } + fp = fopen (filename, "w"); + if (fp != NULL) { + if (fputs (serialized_string, fp) == EOF) { + return_code = JSONFailure; + } + if (fclose (fp) == EOF) { + return_code = JSONFailure; + } + } + json_free_serialized_string(serialized_string); + return return_code; +} + +char * json_serialize_to_string(const JSON_Value *value) { + JSON_Status serialization_result = JSONFailure; + size_t buf_size_bytes = json_serialization_size(value); + char *buf = (char*)PARSON_MALLOC(buf_size_bytes); + if (buf == NULL) + return NULL; + serialization_result = json_serialize_to_buffer(value, buf, buf_size_bytes); + if (serialization_result == JSONFailure) { + json_free_serialized_string(buf); + return NULL; + } + return buf; +} + +void json_free_serialized_string(char *string) { + PARSON_FREE(string); +} + +JSON_Status json_array_remove(JSON_Array *array, size_t ix) { + size_t last_element_ix = 0; + if (array == NULL || ix >= json_array_get_count(array)) { + return JSONFailure; + } + last_element_ix = json_array_get_count(array) - 1; + json_value_free(json_array_get_value(array, ix)); + array->count -= 1; + if (ix != last_element_ix) /* Replace value with one from the end of array */ + array->items[ix] = json_array_get_value(array, last_element_ix); + return JSONSuccess; +} + +JSON_Status json_array_replace_value(JSON_Array *array, size_t ix, JSON_Value *value) { + if (array == NULL || value == NULL || ix >= json_array_get_count(array)) { + return JSONFailure; + } + json_value_free(json_array_get_value(array, ix)); + array->items[ix] = value; + return JSONSuccess; +} + +JSON_Status json_array_replace_string(JSON_Array *array, size_t i, const char* string) { + return json_array_replace_value(array, i, json_value_init_string(string)); +} + +JSON_Status json_array_replace_number(JSON_Array *array, size_t i, double number) { + return json_array_replace_value(array, i, json_value_init_number(number)); +} + +JSON_Status json_array_replace_boolean(JSON_Array *array, size_t i, int boolean) { + return json_array_replace_value(array, i, json_value_init_boolean(boolean)); +} + +JSON_Status json_array_replace_null(JSON_Array *array, size_t i) { + return json_array_replace_value(array, i, json_value_init_null()); +} + +JSON_Status json_array_clear(JSON_Array *array) { + size_t i = 0; + if (array == NULL) + return JSONFailure; + for (i = 0; i < json_array_get_count(array); i++) { + json_value_free(json_array_get_value(array, i)); + } + array->count = 0; + return JSONSuccess; +} + +JSON_Status json_array_append_value(JSON_Array *array, JSON_Value *value) { + if (array == NULL || value == NULL) + return JSONFailure; + return json_array_add(array, value); +} + +JSON_Status json_array_append_string(JSON_Array *array, const char *string) { + return json_array_append_value(array, json_value_init_string(string)); +} + +JSON_Status json_array_append_number(JSON_Array *array, double number) { + return json_array_append_value(array, json_value_init_number(number)); +} + +JSON_Status json_array_append_boolean(JSON_Array *array, int boolean) { + return json_array_append_value(array, json_value_init_boolean(boolean)); +} + +JSON_Status json_array_append_null(JSON_Array *array) { + return json_array_append_value(array, json_value_init_null()); +} + +JSON_Status json_object_set_value(JSON_Object *object, const char *name, JSON_Value *value) { + size_t i = 0; + JSON_Value *old_value; + if (object == NULL) + return JSONFailure; + old_value = json_object_get_value(object, name); + if (old_value != NULL) { /* free and overwrite old value */ + json_value_free(old_value); + for (i = 0; i < json_object_get_count(object); i++) { + if (strcmp(object->names[i], name) == 0) { + object->values[i] = value; + return JSONSuccess; + } + } + } + json_object_add(object, name, value); /* add new key value pair */ + return JSONSuccess; +} + +JSON_Status json_object_set_string(JSON_Object *object, const char *name, const char *string) { + return json_object_set_value(object, name, json_value_init_string(string)); +} + +JSON_Status json_object_set_number(JSON_Object *object, const char *name, double number) { + return json_object_set_value(object, name, json_value_init_number(number)); +} + +JSON_Status json_object_set_boolean(JSON_Object *object, const char *name, int boolean) { + return json_object_set_value(object, name, json_value_init_boolean(boolean)); +} + +JSON_Status json_object_set_null(JSON_Object *object, const char *name) { + return json_object_set_value(object, name, json_value_init_null()); +} + +JSON_Status json_object_dotset_value(JSON_Object *object, const char *name, JSON_Value *value) { + char *dot_pos = strchr(name, '.'); + char *current_name = NULL; + JSON_Object *temp_obj = NULL; + JSON_Value *new_value = NULL; + if (value == NULL) { + return JSONFailure; + } else if (dot_pos == NULL) { + return json_object_set_value(object, name, value); + } else { + current_name = parson_strndup(name, dot_pos - name); + temp_obj = json_object_get_object(object, current_name); + if (temp_obj == NULL) { + new_value = json_value_init_object(); + if (new_value == NULL) { + PARSON_FREE(current_name); + return JSONFailure; + } + if (json_object_add(object, current_name, new_value) == JSONFailure) { + json_value_free(new_value); + PARSON_FREE(current_name); + return JSONFailure; + } + temp_obj = json_object_get_object(object, current_name); + } + PARSON_FREE(current_name); + return json_object_dotset_value(temp_obj, dot_pos + 1, value); + } + return JSONFailure; +} + +JSON_Status json_object_dotset_string(JSON_Object *object, const char *name, const char *string) { + return json_object_dotset_value(object, name, json_value_init_string(string)); +} + +JSON_Status json_object_dotset_number(JSON_Object *object, const char *name, double number) { + return json_object_dotset_value(object, name, json_value_init_number(number)); +} + +JSON_Status json_object_dotset_boolean(JSON_Object *object, const char *name, int boolean) { + return json_object_dotset_value(object, name, json_value_init_boolean(boolean)); +} + +JSON_Status json_object_dotset_null(JSON_Object *object, const char *name) { + return json_object_dotset_value(object, name, json_value_init_null()); +} + +JSON_Status json_object_remove(JSON_Object *object, const char *name) { + size_t i = 0, last_item_index = 0; + if (object == NULL || json_object_get_value(object, name) == NULL) + return JSONFailure; + last_item_index = json_object_get_count(object) - 1; + for (i = 0; i < json_object_get_count(object); i++) { + if (strcmp(object->names[i], name) == 0) { + PARSON_FREE(object->names[i]); + json_value_free(object->values[i]); + if (i != last_item_index) { /* Replace key value pair with one from the end */ + object->names[i] = object->names[last_item_index]; + object->values[i] = object->values[last_item_index]; + } + object->count -= 1; + return JSONSuccess; + } + } + return JSONFailure; /* No execution path should end here */ +} + +JSON_Status json_object_dotremove(JSON_Object *object, const char *name) { + char *dot_pos = strchr(name, '.'); + char *current_name = NULL; + JSON_Object *temp_obj = NULL; + if (dot_pos == NULL) { + return json_object_remove(object, name); + } else { + current_name = parson_strndup(name, dot_pos - name); + temp_obj = json_object_get_object(object, current_name); + if (temp_obj == NULL) { + PARSON_FREE(current_name); + return JSONFailure; + } + PARSON_FREE(current_name); + return json_object_dotremove(temp_obj, dot_pos + 1); + } + return JSONFailure; +} + +JSON_Status json_object_clear(JSON_Object *object) { + size_t i = 0; + if (object == NULL) { + return JSONFailure; + } + for (i = 0; i < json_object_get_count(object); i++) { + PARSON_FREE(object->names[i]); + json_value_free(object->values[i]); + } + object->count = 0; + return JSONSuccess; +} + +JSON_Status json_validate(const JSON_Value *schema, const JSON_Value *value) { + JSON_Value *temp_schema_value = NULL, *temp_value = NULL; + JSON_Array *schema_array = NULL, *value_array = NULL; + JSON_Object *schema_object = NULL, *value_object = NULL; + JSON_Value_Type schema_type = JSONError, value_type = JSONError; + const char *key = NULL; + size_t i = 0, count = 0; + if (schema == NULL || value == NULL) + return JSONFailure; + schema_type = json_value_get_type(schema); + value_type = json_value_get_type(value); + if (schema_type != value_type && schema_type != JSONNull) /* null represents all values */ + return JSONFailure; + switch (schema_type) { + case JSONArray: + schema_array = json_value_get_array(schema); + value_array = json_value_get_array(value); + count = json_array_get_count(schema_array); + if (count == 0) + return JSONSuccess; /* Empty array allows all types */ + /* Get first value from array, rest is ignored */ + temp_schema_value = json_array_get_value(schema_array, 0); + for (i = 0; i < json_array_get_count(value_array); i++) { + temp_value = json_array_get_value(value_array, i); + if (json_validate(temp_schema_value, temp_value) == 0) { + return JSONFailure; + } + } + return JSONSuccess; + case JSONObject: + schema_object = json_value_get_object(schema); + value_object = json_value_get_object(value); + count = json_object_get_count(schema_object); + if (count == 0) + return JSONSuccess; /* Empty object allows all objects */ + else if (json_object_get_count(value_object) < count) + return JSONFailure; /* Tested object mustn't have less name-value pairs than schema */ + for (i = 0; i < count; i++) { + key = json_object_get_name(schema_object, i); + temp_schema_value = json_object_get_value(schema_object, key); + temp_value = json_object_get_value(value_object, key); + if (temp_value == NULL) + return JSONFailure; + if (json_validate(temp_schema_value, temp_value) == JSONFailure) + return JSONFailure; + } + return JSONSuccess; + case JSONString: case JSONNumber: case JSONBoolean: case JSONNull: + return JSONSuccess; /* equality already tested before switch */ + case JSONError: default: + return JSONFailure; + } +} + +JSON_Status json_value_equals(const JSON_Value *a, const JSON_Value *b) { + JSON_Object *a_object = NULL, *b_object = NULL; + JSON_Array *a_array = NULL, *b_array = NULL; + const char *a_string = NULL, *b_string = NULL; + const char *key = NULL; + size_t a_count = 0, b_count = 0, i = 0; + JSON_Value_Type a_type, b_type; + a_type = json_value_get_type(a); + b_type = json_value_get_type(b); + if (a_type != b_type) { + return 0; + } + switch (a_type) { + case JSONArray: + a_array = json_value_get_array(a); + b_array = json_value_get_array(b); + a_count = json_array_get_count(a_array); + b_count = json_array_get_count(b_array); + if (a_count != b_count) { + return 0; + } + for (i = 0; i < a_count; i++) { + if (!json_value_equals(json_array_get_value(a_array, i), + json_array_get_value(b_array, i))) { + return 0; + } + } + return 1; + case JSONObject: + a_object = json_value_get_object(a); + b_object = json_value_get_object(b); + a_count = json_object_get_count(a_object); + b_count = json_object_get_count(b_object); + if (a_count != b_count) { + return 0; + } + for (i = 0; i < a_count; i++) { + key = json_object_get_name(a_object, i); + if (!json_value_equals(json_object_get_value(a_object, key), + json_object_get_value(b_object, key))) { + return 0; + } + } + return 1; + case JSONString: + a_string = json_value_get_string(a); + b_string = json_value_get_string(b); + return strcmp(a_string, b_string) == 0; + case JSONBoolean: + return json_value_get_boolean(a) == json_value_get_boolean(b); + case JSONNumber: + return fabs(json_value_get_number(a) - json_value_get_number(b)) < 0.000001; /* EPSILON */ + case JSONError: + return 1; + case JSONNull: + return 1; + default: + return 1; + } +} + +JSON_Value_Type json_type(const JSON_Value *value) { + return json_value_get_type(value); +} + +JSON_Object * json_object (const JSON_Value *value) { + return json_value_get_object(value); +} + +JSON_Array * json_array (const JSON_Value *value) { + return json_value_get_array(value); +} + +const char * json_string (const JSON_Value *value) { + return json_value_get_string(value); +} + +double json_number (const JSON_Value *value) { + return json_value_get_number(value); +} + +int json_boolean(const JSON_Value *value) { + return json_value_get_boolean(value); } diff --git a/parson.h b/parson.h old mode 100644 new mode 100755 index be3dd11..5d6b870 --- a/parson.h +++ b/parson.h @@ -36,38 +36,68 @@ typedef struct json_object_t JSON_Object; typedef struct json_array_t JSON_Array; typedef struct json_value_t JSON_Value; -typedef enum json_value_type { - JSONError = 0, +enum json_value_type { + JSONError = -1, JSONNull = 1, JSONString = 2, JSONNumber = 3, JSONObject = 4, JSONArray = 5, JSONBoolean = 6 -} JSON_Value_Type; - +}; +typedef int JSON_Value_Type; + +enum json_result_t { + JSONSuccess = 0, + JSONFailure = -1 +}; +typedef int JSON_Status; /* Parses first JSON value in a file, returns NULL in case of error */ -JSON_Value * json_parse_file(const char *filename); +JSON_Value * json_parse_file(const char *filename); /* Parses first JSON value in a file and ignores comments (/ * * / and //), returns NULL in case of error */ -JSON_Value * json_parse_file_with_comments(const char *filename); +JSON_Value * json_parse_file_with_comments(const char *filename); /* Parses first JSON value in a string, returns NULL in case of error */ -JSON_Value * json_parse_string(const char *string); +JSON_Value * json_parse_string(const char *string); /* Parses first JSON value in a string and ignores comments (/ * * / and //), returns NULL in case of error */ -JSON_Value * json_parse_string_with_comments(const char *string); +JSON_Value * json_parse_string_with_comments(const char *string); + +/* Serialization */ +size_t json_serialization_size(const JSON_Value *value); +JSON_Status json_serialize_to_buffer(const JSON_Value *value, char *buf, size_t buf_size_in_bytes); +JSON_Status json_serialize_to_file(const JSON_Value *value, const char *filename); +char * json_serialize_to_string(const JSON_Value *value); +void json_free_serialized_string(char *string); /* frees string from json_serialize_to_string */ + +/* Comparing */ +int json_value_equals(const JSON_Value *a, const JSON_Value *b); + +/* Validation + This is *NOT* JSON Schema. It validates json by checking if object have identically + named fields with matching types. + For example schema {"name":"", "age":0} will validate + {"name":"Joe", "age":25} and {"name":"Joe", "age":25, "gender":"m"}, + but not {"name":"Joe"} or {"name":"Joe", "age":"Cucumber"}. + In case of arrays, only first value in schema is checked against all values in tested array. + Empty objects ({}) validate all objects, empty arrays ([]) validate all arrays, + null validates values of every type. + */ +JSON_Status json_validate(const JSON_Value *schema, const JSON_Value *value); -/* JSON Object */ +/* + * JSON Object + */ JSON_Value * json_object_get_value (const JSON_Object *object, const char *name); const char * json_object_get_string (const JSON_Object *object, const char *name); JSON_Object * json_object_get_object (const JSON_Object *object, const char *name); JSON_Array * json_object_get_array (const JSON_Object *object, const char *name); -double json_object_get_number (const JSON_Object *object, const char *name); -int json_object_get_boolean(const JSON_Object *object, const char *name); +double json_object_get_number (const JSON_Object *object, const char *name); /* returns 0 on fail */ +int json_object_get_boolean(const JSON_Object *object, const char *name); /* returns -1 on fail */ /* dotget functions enable addressing values with dot notation in nested objects, just like in structs or c++/java/c# objects (e.g. objectA.objectB.value). @@ -77,30 +107,95 @@ JSON_Value * json_object_dotget_value (const JSON_Object *object, const char * const char * json_object_dotget_string (const JSON_Object *object, const char *name); JSON_Object * json_object_dotget_object (const JSON_Object *object, const char *name); JSON_Array * json_object_dotget_array (const JSON_Object *object, const char *name); -double json_object_dotget_number (const JSON_Object *object, const char *name); -int json_object_dotget_boolean(const JSON_Object *object, const char *name); +double json_object_dotget_number (const JSON_Object *object, const char *name); /* returns 0 on fail */ +int json_object_dotget_boolean(const JSON_Object *object, const char *name); /* returns -1 on fail */ /* Functions to get available names */ size_t json_object_get_count(const JSON_Object *object); const char * json_object_get_name (const JSON_Object *object, size_t index); -/* JSON Array */ +/* Creates new name-value pair or frees and replaces old value with new one. */ +JSON_Status json_object_set_value(JSON_Object *object, const char *name, JSON_Value *value); +JSON_Status json_object_set_string(JSON_Object *object, const char *name, const char *string); +JSON_Status json_object_set_number(JSON_Object *object, const char *name, double number); +JSON_Status json_object_set_boolean(JSON_Object *object, const char *name, int boolean); +JSON_Status json_object_set_null(JSON_Object *object, const char *name); + +/* Works like dotget functions, but creates whole hierarchy if necessary. */ +JSON_Status json_object_dotset_value(JSON_Object *object, const char *name, JSON_Value *value); +JSON_Status json_object_dotset_string(JSON_Object *object, const char *name, const char *string); +JSON_Status json_object_dotset_number(JSON_Object *object, const char *name, double number); +JSON_Status json_object_dotset_boolean(JSON_Object *object, const char *name, int boolean); +JSON_Status json_object_dotset_null(JSON_Object *object, const char *name); + +/* Frees and removes name-value pair */ +JSON_Status json_object_remove(JSON_Object *object, const char *name); + +/* Works like dotget function, but removes name-value pair only on exact match. */ +JSON_Status json_object_dotremove(JSON_Object *object, const char *key); + +/* Removes all name-value pairs in object */ +JSON_Status json_object_clear(JSON_Object *object); + +/* + *JSON Array + */ JSON_Value * json_array_get_value (const JSON_Array *array, size_t index); const char * json_array_get_string (const JSON_Array *array, size_t index); JSON_Object * json_array_get_object (const JSON_Array *array, size_t index); JSON_Array * json_array_get_array (const JSON_Array *array, size_t index); -double json_array_get_number (const JSON_Array *array, size_t index); -int json_array_get_boolean(const JSON_Array *array, size_t index); +double json_array_get_number (const JSON_Array *array, size_t index); /* returns 0 on fail */ +int json_array_get_boolean(const JSON_Array *array, size_t index); /* returns -1 on fail */ size_t json_array_get_count (const JSON_Array *array); + +/* Frees and removes value at given index, does nothing and returns JSONFailure if index doesn't exist. + * Order of values in array may change during execution. */ +JSON_Status json_array_remove(JSON_Array *array, size_t i); + +/* Frees and removes from array value at given index and replaces it with given one. + * Does nothing and returns JSONFailure if index doesn't exist. */ +JSON_Status json_array_replace_value(JSON_Array *array, size_t i, JSON_Value *value); +JSON_Status json_array_replace_string(JSON_Array *array, size_t i, const char* string); +JSON_Status json_array_replace_number(JSON_Array *array, size_t i, double number); +JSON_Status json_array_replace_boolean(JSON_Array *array, size_t i, int boolean); +JSON_Status json_array_replace_null(JSON_Array *array, size_t i); + +/* Frees and removes all values from array */ +JSON_Status json_array_clear(JSON_Array *array); + +/* Appends new value at the end of array. */ +JSON_Status json_array_append_value(JSON_Array *array, JSON_Value *value); +JSON_Status json_array_append_string(JSON_Array *array, const char *string); +JSON_Status json_array_append_number(JSON_Array *array, double number); +JSON_Status json_array_append_boolean(JSON_Array *array, int boolean); +JSON_Status json_array_append_null(JSON_Array *array); + +/* + *JSON Value + */ +JSON_Value * json_value_init_object (void); +JSON_Value * json_value_init_array (void); +JSON_Value * json_value_init_string (const char *string); /* copies passed string */ +JSON_Value * json_value_init_number (double number); +JSON_Value * json_value_init_boolean(int boolean); +JSON_Value * json_value_init_null (void); +JSON_Value * json_value_deep_copy (const JSON_Value *value); +void json_value_free (JSON_Value *value); -/* JSON Value */ JSON_Value_Type json_value_get_type (const JSON_Value *value); JSON_Object * json_value_get_object (const JSON_Value *value); JSON_Array * json_value_get_array (const JSON_Value *value); const char * json_value_get_string (const JSON_Value *value); double json_value_get_number (const JSON_Value *value); int json_value_get_boolean(const JSON_Value *value); -void json_value_free (JSON_Value *value); + +/* Same as above, but shorter */ +JSON_Value_Type json_type (const JSON_Value *value); +JSON_Object * json_object (const JSON_Value *value); +JSON_Array * json_array (const JSON_Value *value); +const char * json_string (const JSON_Value *value); +double json_number (const JSON_Value *value); +int json_boolean(const JSON_Value *value); #ifdef __cplusplus } diff --git a/tests.c b/tests.c old mode 100644 new mode 100755 index ec56759..dd27f98 --- a/tests.c +++ b/tests.c @@ -20,6 +20,9 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +#ifdef _MSC_VER +#define _CRT_SECURE_NO_WARNINGS +#endif #include "parson.h" @@ -30,53 +33,68 @@ #define TEST(A) printf("%-72s-",#A); \ if(A){puts(" OK");tests_passed++;} \ else{puts(" FAIL");tests_failed++;} -#define STREQ(A, B) (A && B ? strcmp(A, B) == 0 : 0) +#define STREQ(A, B) ((A) && (B) ? strcmp((A), (B)) == 0 : 0) - -void test_suite_1(void); -void test_suite_2(JSON_Value *value); +void test_suite_1(void); /* Test 3 files from json.org + serialization*/ +void test_suite_2(JSON_Value *value); /* Test correctness of parsed values */ void test_suite_2_no_comments(void); void test_suite_2_with_comments(void); -void test_suite_3(void); +void test_suite_3(void); /* Test incorrect values */ +void test_suite_4(void); /* Test deep copy funtion */ +void test_suite_5(void); /* Test building json values from scratch */ +void test_suite_6(void); /* Test value comparing verification */ +void test_suite_7(void); /* Test schema validation */ +void test_suite_8(void); /* Test serialization to file */ -char *read_file(const char *filename); void print_commits_info(const char *username, const char *repo); +void persistence_example(void); +void serialization_example(void); static int tests_passed; static int tests_failed; int main() { - /* Example function from readme file: */ + /* Example functions from readme file: */ /* print_commits_info("torvalds", "linux"); */ + /* serialization_example(); */ + /* persistence_example(); */ test_suite_1(); test_suite_2_no_comments(); test_suite_2_with_comments(); test_suite_3(); + test_suite_4(); + test_suite_5(); + test_suite_6(); + test_suite_7(); + test_suite_8(); printf("Tests failed: %d\n", tests_failed); printf("Tests passed: %d\n", tests_passed); return 0; } -/* 3 test files from json.org */ void test_suite_1(void) { JSON_Value *val; TEST((val = json_parse_file("tests/test_1_1.txt")) != NULL); + TEST(json_value_equals(json_parse_string(json_serialize_to_string(val)), val)); if (val) { json_value_free(val); } TEST((val = json_parse_file("tests/test_1_2.txt")) != NULL); + TEST(json_value_equals(json_parse_string(json_serialize_to_string(val)), val)); if (val) { json_value_free(val); } TEST((val = json_parse_file("tests/test_1_3.txt")) != NULL); + TEST(json_value_equals(json_parse_string(json_serialize_to_string(val)), val)); if (val) { json_value_free(val); } TEST((val = json_parse_file_with_comments("tests/test_1_1.txt")) != NULL); + TEST(json_value_equals(json_parse_string(json_serialize_to_string(val)), val)); if (val) { json_value_free(val); } TEST((val = json_parse_file_with_comments("tests/test_1_2.txt")) != NULL); + TEST(json_value_equals(json_parse_string(json_serialize_to_string(val)), val)); if (val) { json_value_free(val); } TEST((val = json_parse_file_with_comments("tests/test_1_3.txt")) != NULL); + TEST(json_value_equals(json_parse_string(json_serialize_to_string(val)), val)); if (val) { json_value_free(val); } - } -/* Testing correctness of parsed values */ void test_suite_2(JSON_Value *root_value) { JSON_Object *root_object; JSON_Array *array; @@ -125,11 +143,11 @@ void test_suite_2(JSON_Value *root_value) { TEST(json_object_dotget_value(root_object, "") == NULL); array = json_object_dotget_array(root_object, "object.nested array"); + TEST(array != NULL); + TEST(json_array_get_count(array) > 1); if (array != NULL && json_array_get_count(array) > 1) { TEST(STREQ(json_array_get_string(array, 0), "lorem")); TEST(STREQ(json_array_get_string(array, 1), "ipsum")); - } else { - tests_failed++; } TEST(json_object_dotget_boolean(root_object, "nested true")); @@ -142,22 +160,21 @@ void test_suite_2(JSON_Value *root_value) { void test_suite_2_no_comments(void) { const char *filename = "tests/test_2.txt"; JSON_Value *root_value = NULL; - printf("Testing %s:\n", filename); root_value = json_parse_file(filename); test_suite_2(root_value); + TEST(json_value_equals(root_value, json_parse_string(json_serialize_to_string(root_value)))); json_value_free(root_value); } void test_suite_2_with_comments(void) { const char *filename = "tests/test_2_comments.txt"; JSON_Value *root_value = NULL; - printf("Testing %s:\n", filename); root_value = json_parse_file_with_comments(filename); test_suite_2(root_value); + TEST(json_value_equals(root_value, json_parse_string(json_serialize_to_string(root_value)))); json_value_free(root_value); } -/* Testing values, on which parsing should fail */ void test_suite_3(void) { char nested_20x[] = "[[[[[[[[[[[[[[[[[[[[\"hi\"]]]]]]]]]]]]]]]]]]]]"; puts("Testing invalid strings:"); @@ -205,6 +222,80 @@ void test_suite_3(void) { TEST(json_parse_string("[\"\\uDF67\\uD834\"]") == NULL); /* wrong order surrogate pair */ } +void test_suite_4() { + const char *filename = "tests/test_2.txt"; + JSON_Value *a = NULL, *a_copy = NULL; + printf("Testing %s:\n", filename); + a = json_parse_file(filename); + TEST(json_value_equals(a, a)); /* test equality test */ + a_copy = json_value_deep_copy(a); + TEST(a_copy != NULL); + TEST(json_value_equals(a, a_copy)); +} + +void test_suite_5(void) { + JSON_Value *val_from_file = json_parse_file("tests/test_5.txt"); + + JSON_Value *val = json_value_init_object(); + JSON_Object *obj = json_value_get_object(val); + TEST(json_object_set_string(obj, "first", "John") == JSONSuccess); + TEST(json_object_set_string(obj, "last", "Doe") == JSONSuccess); + TEST(json_object_set_number(obj, "age", 25) == JSONSuccess); + TEST(json_object_set_boolean(obj, "registered", 1) == JSONSuccess); + TEST(json_object_set_value(obj, "interests", json_value_init_array()) == JSONSuccess); + TEST(json_array_append_string(json_object_get_array(obj, "interests"), "Writing") == JSONSuccess); + TEST(json_array_append_string(json_object_get_array(obj, "interests"), "Mountain Biking") == JSONSuccess); + TEST(json_array_replace_string(json_object_get_array(obj, "interests"), 0, "Reading") == JSONSuccess); + TEST(json_object_dotset_string(obj, "favorites.color", "blue") == JSONSuccess); + TEST(json_object_dotset_string(obj, "favorites.sport", "running") == JSONSuccess); + TEST(json_object_dotset_string(obj, "favorites.fruit", "apple") == JSONSuccess); + TEST(json_object_dotremove(obj, "favorites.fruit") == JSONSuccess); + TEST(json_object_set_string(obj, "utf string", "\\u006corem\\u0020ipsum") == JSONSuccess); + TEST(json_object_set_string(obj, "utf-8 string", "あいうえお") == JSONSuccess); + TEST(json_object_set_string(obj, "surrogate string", "lorem\\uD834\\uDD1Eipsum\\uD834\\uDF67lorem") == JSONSuccess); + TEST(json_value_equals(val_from_file, val)); +} + +void test_suite_6(void) { + const char *filename = "tests/test_2.txt"; + JSON_Value *a = NULL; + JSON_Value *b = NULL; + a = json_parse_file(filename); + b = json_parse_file(filename); + TEST(json_value_equals(a, b)); + json_object_set_string(json_object(a), "string", "eki"); + TEST(!json_value_equals(a, b)); + a = json_value_deep_copy(b); + TEST(json_value_equals(a, b)); + json_array_append_number(json_object_get_array(json_object(b), "string array"), 1337); + TEST(!json_value_equals(a, b)); +} + +void test_suite_7(void) { + JSON_Value *val_from_file = json_parse_file("tests/test_5.txt"); + JSON_Value *schema = json_value_init_object(); + JSON_Object *schema_obj = json_value_get_object(schema); + json_object_set_string(schema_obj, "first", ""); + json_object_set_string(schema_obj, "last", ""); + json_object_set_number(schema_obj, "age", 0); + json_object_set_null(schema_obj, "favorites"); + TEST(json_validate(schema, val_from_file) == JSONSuccess); + json_object_set_string(schema_obj, "age", ""); + TEST(json_validate(schema, val_from_file) == JSONFailure); +} + +void test_suite_8(void) { + const char *filename = "tests/test_2.txt"; + const char *temp_filename = "tests/test_2_serialized.txt"; + JSON_Value *a = NULL; + JSON_Value *b = NULL; + a = json_parse_file(filename); + TEST(json_serialize_to_file(a, temp_filename) == JSONSuccess); + b = json_parse_file(temp_filename); + TEST(json_value_equals(a, b)); + remove(temp_filename); +} + void print_commits_info(const char *username, const char *repo) { JSON_Value *root_value; JSON_Array *commits; @@ -244,3 +335,36 @@ void print_commits_info(const char *username, const char *repo) { json_value_free(root_value); system(cleanup_command); } + +void persistence_example(void) { + JSON_Value *schema = json_parse_string("{\"name\":\"\"}"); + JSON_Value *user_data = json_parse_file("user_data.json"); + char buf[256]; + const char *name = NULL; + if (!user_data || json_validate(schema, user_data) == JSONSuccess) { + puts("Enter your name:"); + scanf("%s", buf); + user_data = json_value_init_object(); + json_object_set_string(json_object(user_data), "name", buf); + json_serialize_to_file(user_data, "user_data.json"); + } + name = json_object_get_string(json_object(user_data), "name"); + printf("Hello, %s.", name); + json_value_free(schema); + json_value_free(user_data); + return; +} + +void serialization_example(void) { + JSON_Value *root_value = json_value_init_object(); + JSON_Object *root_object = json_value_get_object(root_value); + char *serialized_string = NULL; + json_object_set_string(root_object, "name", "John Smith"); + json_object_set_number(root_object, "age", 25); + json_object_dotset_string(root_object, "address.city", "Cupertino"); + json_object_dotset_value(root_object, "contact.emails", + json_parse_string("[\"email@example.com\", \"email2@example.com\"]")); + serialized_string = json_serialize_to_string(root_value); + puts(serialized_string); + json_free_serialized_string(serialized_string); +} diff --git a/tests/test_1_1.txt b/tests/test_1_1.txt old mode 100644 new mode 100755 diff --git a/tests/test_1_2.txt b/tests/test_1_2.txt old mode 100644 new mode 100755 diff --git a/tests/test_1_3.txt b/tests/test_1_3.txt old mode 100644 new mode 100755 diff --git a/tests/test_2.txt b/tests/test_2.txt old mode 100644 new mode 100755 diff --git a/tests/test_2_comments.txt b/tests/test_2_comments.txt old mode 100644 new mode 100755 diff --git a/tests/test_5.txt b/tests/test_5.txt new file mode 100644 index 0000000..6e71331 --- /dev/null +++ b/tests/test_5.txt @@ -0,0 +1,14 @@ +{ + "first": "John", + "last": "Doe", + "age": 25, + "registered": true, + "interests": [ "Reading", "Mountain Biking" ], + "favorites": { + "color": "blue", + "sport": "running" + }, + "utf string" : "\u006corem\u0020ipsum", + "utf-8 string": "あいうえお", + "surrogate string": "lorem\uD834\uDD1Eipsum\uD834\uDF67lorem" +} \ No newline at end of file