From adefffe1b08311bc05c8558fe844abf44565431a Mon Sep 17 00:00:00 2001 From: TheTechsTech Date: Sat, 23 Dec 2023 11:53:22 -0500 Subject: [PATCH] add various json routines into single functions. - add `json_encode` for object creation functions, using `printf` like formats. - add `json_for` for array creation functions, using `printf` like formats. - add `is_json` basic check not NULL and type not JSONError. - add `json_decode` combines` json_parse_string_with_comments` and `json_parse_string` - add `json_serialize` combines `json_serialize_to_string_pretty` and `json_serialize_to_string` - add `encode_decode_example()` to readme, show shorter syntax to `serialization_example()` - add/enable `encode_decode_example()` into tests.c Note: This library is embed into https://github.com/zelang-dev/c-coroutine/blob/main/src/json.c and https://github.com/zelang-dev/c-coroutine/blob/main/examples/co_json.c where all memory and I/O is managed difference. --- README.md | 51 +++++++++--- parson.c | 236 +++++++++++++++++++++++++++++++++++++++++++++++++++++- parson.h | 55 ++++++++++++- tests.c | 24 ++++++ 4 files changed, 351 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 011e051..9ba338d 100644 --- a/README.md +++ b/README.md @@ -19,32 +19,32 @@ Run ```make test``` to compile and run tests. ## Examples ### Parsing JSON -Here is a function, which prints basic commit info (date, sha and author) from a github repository. +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; JSON_Array *commits; JSON_Object *commit; size_t i; - + char curl_command[512]; char cleanup_command[256]; char output_filename[] = "commits.json"; - + /* it ain't pretty, but it's not a libcurl tutorial */ - sprintf(curl_command, + sprintf(curl_command, "curl -s \"https://api.github.com/repos/%s/%s/commits\" > %s", username, repo, output_filename); sprintf(cleanup_command, "rm -f %s", output_filename); system(curl_command); - + /* parsing json and validating output */ root_value = json_parse_file(output_filename); if (json_value_get_type(root_value) != JSONArray) { system(cleanup_command); return; } - + /* getting array from root value and printing commit info */ commits = json_value_get_array(root_value); printf("%-10.10s %-10.10s %s\n", "Date", "SHA", "Author"); @@ -55,14 +55,14 @@ void print_commits_info(const char *username, const char *repo) { json_object_get_string(commit, "sha"), json_object_dotget_string(commit, "commit.author.name")); } - + /* cleanup code */ json_value_free(root_value); system(cleanup_command); } ``` -Calling ```print_commits_info("torvalds", "linux");``` prints: +Calling ```print_commits_info("torvalds", "linux");``` prints: ``` Date SHA Author 2012-10-15 dd8e8c4a2c David Rientjes @@ -98,8 +98,8 @@ void persistence_example(void) { ``` ### Serialization -Creating JSON values is very simple thanks to the dot notation. -Object hierarchy is automatically created when addressing specific fields. +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) { @@ -135,6 +135,35 @@ Output: } ``` +The above can also be achieved using `json_encode("printf like format", ...)` for objects, and `json_for("printf like format", ...)` for arrays. + +```c +// #ifndef kv +// #define kv(key, value) (key), (value) +// #endif + +void encode_decode_example(void) { + // JSON_Value *value = json_for("ss", "email@example.com", "email2@example.com"); // same as "[\"email@example.com\",\"email2@example.com\"]" + // char *serialized_for = json_serialize(value, false); + char *serialized_string = NULL; + JSON_Value *root_value = json_encode("si.s.v", + kv("name", "John Smith"), + kv("age", 25), + kv("address.city", "Cupertino"), + kv("contact.emails", json_decode("[\"email@example.com\",\"email2@example.com\"]", false))); + + //if (is_json(root_value)) { + serialized_string = json_serialize(root_value, true); + puts(serialized_string); + // } + + // json_free_serialized_string(serialized_for); + json_free_serialized_string(serialized_string); + json_value_free(root_value); + // json_value_free(value); +} +``` + ## Contributing I will always merge *working* bug fixes. However, if you want to add something new to the API, please create an "issue" on github for this first so we can discuss if it should end up in the library before you start implementing it. @@ -142,7 +171,7 @@ Remember to follow parson's code style and write appropriate tests. ## My other projects * [ape](https://github.com/kgabis/ape) - simple programming language implemented in C library -* [kgflags](https://github.com/kgabis/kgflags) - easy to use command-line flag parsing library +* [kgflags](https://github.com/kgabis/kgflags) - easy to use command-line flag parsing library * [agnes](https://github.com/kgabis/agnes) - header-only NES emulation library ## License diff --git a/parson.c b/parson.c index 526aab4..9d13880 100644 --- a/parson.c +++ b/parson.c @@ -290,7 +290,7 @@ static int parson_sprintf(char * s, const char * format, ...) { int result; va_list args; va_start(args, format); - + #if defined(__APPLE__) && defined(__clang__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" @@ -2176,7 +2176,7 @@ JSON_Status json_object_dotset_value(JSON_Object *object, const char *name, JSON JSON_Status status = JSONFailure; size_t name_len = 0; char *name_copy = NULL; - + if (object == NULL || name == NULL || value == NULL) { return JSONFailure; } @@ -2296,7 +2296,7 @@ JSON_Status json_object_clear(JSON_Object *object) { for (i = 0; i < json_object_get_count(object); i++) { parson_free(object->names[i]); object->names[i] = NULL; - + json_value_free(object->values[i]); object->values[i] = NULL; } @@ -2484,3 +2484,233 @@ void json_set_float_serialization_format(const char *format) { void json_set_number_serialization_function(JSON_Number_Serialization_Function func) { parson_number_serialization_function = func; } + +bool is_json(JSON_Value *schema) { + return (schema == NULL || json_value_get_type(schema) == JSONError) ? false : true; +} + +char *json_serialize(JSON_Value *value, bool is_pretty) { + char *json_string = NULL; + if (value != NULL) { + if (is_pretty) + json_string = json_serialize_to_string_pretty(value); + else + json_string = json_serialize_to_string(value); + } + + return json_string; +} + +JSON_Value *json_decode(const char *text, bool is_commented) { + if (is_commented) + return json_parse_string_with_comments(text); + else + return json_parse_string(text); +} + +JSON_Value *json_encode(const char *desc, ...) { + int count = (int)strlen(desc); + JSON_Value *json_root = json_value_init_object(); + JSON_Object *json_object = json_value_get_object(json_root); + + va_list argp; + char *key, *value_char; + int value_bool; + JSON_Status status = JSONSuccess; + void *value_any = NULL; + JSON_Array *value_array = NULL; + double value_float = 0; + long value_int = 0; + size_t value_max = 0; + bool is_dot = false, is_array = false, is_double = false, is_int = false, is_max = false; + + va_start(argp, desc); + for (int i = 0; i < count; i++) { + if (status == JSONFailure) + return NULL; + + switch (*desc++) { + case '.': + is_dot = true; + break; + case 'e': + if (is_array) { + is_array = false; + value_array = NULL; + is_dot = false; + } + break; + case 'a': + if (!is_array) { + key = va_arg(argp, char *); + status = json_object_set_value(json_object, key, json_value_init_array()); + value_array = json_object_get_array(json_object, key); + is_array = true; + is_dot = false; + } + break; + case 'n': + if (!is_array) + key = va_arg(argp, char *); + + if (is_array) + status = json_array_append_null(value_array); + else if (is_dot) + status = json_object_dotset_null(json_object, key); + else + status = json_object_set_null(json_object, key); + is_dot = false; + break; + case 'd': + is_int = true; + case 'f': + if (!is_int) + is_double = true; + case 'i': + if (!is_double && !is_int) + is_max = true; + + if (!is_array) + key = va_arg(argp, char *); + + if (is_double) + value_float = va_arg(argp, double); + else if (is_int) + value_int = va_arg(argp, long); + else + value_max = va_arg(argp, size_t); + + if (is_array) + status = json_array_append_number(value_array, (is_double ? value_float + : is_int ? (int)value_int + : (unsigned long)value_max)); + else if (is_dot) + status = json_object_dotset_number(json_object, key, (is_double ? value_float + : is_int ? (int)value_int + : (unsigned long)value_max)); + else + status = json_object_set_number(json_object, key, (is_double ? value_float + : is_int ? (int)value_int + : (unsigned long)value_max)); + + is_dot = false; + is_double = false; + is_int = false; + is_max = false; + break; + case 'b': + if (!is_array) + key = va_arg(argp, char *); + + value_bool = va_arg(argp, int); + if (is_array) + status = json_array_append_boolean(value_array, value_bool); + else if (is_dot) + status = json_object_dotset_boolean(json_object, key, value_bool); + else + status = json_object_set_boolean(json_object, key, value_bool); + is_dot = false; + break; + case 's': + if (!is_array) + key = va_arg(argp, char *); + + value_char = va_arg(argp, char *); + if (is_array) + status = json_array_append_string(value_array, value_char); + else if (is_dot) + status = json_object_dotset_string(json_object, key, value_char); + else + status = json_object_set_string(json_object, key, value_char); + is_dot = false; + break; + case 'v': + if (!is_array) + key = va_arg(argp, char *); + + value_any = va_arg(argp, void *); + if (is_array) + status = json_array_append_value(value_array, value_any); + else if (is_dot) + status = json_object_dotset_value(json_object, key, value_any); + else + status = json_object_set_value(json_object, key, value_any); + is_dot = false; + break; + default: + break; + } + } + va_end(argp); + + return json_root; +} + +JSON_Value *json_for(const char *desc, ...) { + int count = (int)strlen(desc); + JSON_Value *json_root = json_value_init_object(); + JSON_Object *json_object = json_value_get_object(json_root); + JSON_Status status = json_object_set_value(json_object, "array", json_value_init_array()); + JSON_Array *value_array = json_object_get_array(json_object, "array"); + + va_list argp; + char *value_char; + int value_bool; + void *value_any = NULL; + double value_float = 0; + long value_int = 0; + size_t value_max = 0; + bool is_double = false, is_int = false, is_max = false; + + va_start(argp, desc); + for (int i = 0; i < count; i++) { + if (status == JSONFailure) + return NULL; + + switch (*desc++) { + case 'n': + status = json_array_append_null(value_array); + break; + case 'd': + is_int = true; + case 'f': + if (!is_int) + is_double = true; + case 'i': + if (!is_double && !is_int) + is_max = true; + + if (is_double) + value_float = va_arg(argp, double); + else if (is_int) + value_int = va_arg(argp, long); + else + value_max = va_arg(argp, size_t); + + status = json_array_append_number(value_array, (is_double ? value_float + : is_int ? (int)value_int + : (unsigned long)value_max)); + is_double = false; + is_int = false; + is_max = false; + break; + case 'b': + value_bool = va_arg(argp, int); + status = json_array_append_boolean(value_array, value_bool); + break; + case 's': + value_char = va_arg(argp, char *); + status = json_array_append_string(value_array, value_char); + break; + case 'v': + value_any = va_arg(argp, void *); + status = json_array_append_value(value_array, value_any); + break; + default: + break; + } + } + va_end(argp); + + return json_array_get_wrapping_value(value_array); +} diff --git a/parson.h b/parson.h index 40be490..f1d067a 100644 --- a/parson.h +++ b/parson.h @@ -41,6 +41,7 @@ extern "C" #define PARSON_VERSION_STRING "1.5.3" #include /* size_t */ +#include /* bool */ /* Types and enums */ typedef struct json_object_t JSON_Object; @@ -68,7 +69,7 @@ typedef void * (*JSON_Malloc_Function)(size_t); typedef void (*JSON_Free_Function)(void *); /* A function used for serializing numbers (see json_set_number_serialization_function). - If 'buf' is null then it should return number of bytes that would've been written + If 'buf' is null then it should return number of bytes that would've been written (but not more than PARSON_NUM_BUF_SIZE). */ typedef int (*JSON_Number_Serialization_Function)(double num, char *buf); @@ -267,6 +268,58 @@ size_t json_string_len(const JSON_Value *value); /* doesn't account for double json_number (const JSON_Value *value); int json_boolean(const JSON_Value *value); +/* Check if schema validated by json type */ +bool is_json(JSON_Value *schema); + +/** +* @param value Serialization of value to string. +* @param is_pretty Pretty serialization, if set `true`. +*/ +char *json_serialize(JSON_Value *value, bool is_pretty); + +/** +* @param text Parses first JSON value in a text, returns NULL in case of error. +* @param is_commented Ignores comments (/ * * / and //), if set `true`. +*/ +JSON_Value *json_decode(const char *text, bool is_commented); + +/** +* Creates json value `object` using a format like `printf` for each value to key. +* +* @param desc format string: +* * '`.`' indicate next format character will use dot function to record value for key name with dot, +* * '`a`' begin array encoding, every item `value` will be appended, until '`e`' is place in format desc, +* * '`e`' end array encoding, +* * '`n`' record `null` value for key, *DO NOT PLACE `NULL` IN ARGUMENTS*, +* * '`f`' record `float/double` number for key, +* * '`d`' record `signed` number for key, +* * '`i`' record `unsigned` number for key, +* * '`b`' record `boolean` value for key, +* * '`s`' record `string` value for key, +* * '`v`' record `JSON_Value` for key, +* @param arguments use `kv(key,value)` for pairs, *DO NOT PROVIDE FOR NULL, ONLY KEY* +*/ +JSON_Value *json_encode(const char *desc, ...); + +/** +* Creates json value `array` using a format like `printf` for each value to index. +* +* @param desc format string: +* * '`n`' record `null` value for index, *DO NOT PLACE `NULL` IN ARGUMENTS*, +* * '`f`' record `float/double` number for index, +* * '`d`' record `signed` number for index, +* * '`i`' record `unsigned` number for index, +* * '`b`' record `boolean` value for index, +* * '`s`' record `string` value for index, +* * '`v`' record `JSON_Value` for index, +* @param arguments indexed by `desc` format order, *DO NOT PROVIDE FOR NULL* +*/ +JSON_Value *json_for(const char *desc, ...); + +#ifndef kv +#define kv(key, value) (key), (value) +#endif + #ifdef __cplusplus } #endif diff --git a/tests.c b/tests.c index 3cf97b5..fbefb88 100644 --- a/tests.c +++ b/tests.c @@ -73,6 +73,7 @@ void test_object_clear(void); void print_commits_info(const char *username, const char *repo); void persistence_example(void); void serialization_example(void); +void encode_decode_example(void); static const char *g_tests_path = "tests"; @@ -113,6 +114,8 @@ int tests_main(int argc, char *argv[]) { /* serialization_example(); */ /* persistence_example(); */ + encode_decode_example(); + puts("################################################################################"); puts("Running parson tests"); @@ -830,6 +833,27 @@ void serialization_example(void) { json_value_free(root_value); } +void encode_decode_example(void) { + JSON_Value *value = json_for("ss", "email@example.com", "email2@example.com"); + char *serialized_for = json_serialize(value, false); + char *serialized_string = NULL; + JSON_Value *encoded = json_encode("si.s.v", + kv("name", "John Smith"), + kv("age", 25), + kv("address.city", "Cupertino"), + kv("contact.emails", json_decode(serialized_for, false))); + + if (is_json(encoded)) { + serialized_string = json_serialize(encoded, true); + puts(serialized_string); + } + + json_free_serialized_string(serialized_for); + json_free_serialized_string(serialized_string); + json_value_free(encoded); + json_value_free(value); +} + static char * read_file(const char * file_path) { FILE *fp = NULL; size_t size_to_read = 0;