From 0a98aa0b44dfb6c0bc07d9fa6811135c9a579eb9 Mon Sep 17 00:00:00 2001 From: Dmitriy Musatkin <63878209+DmitriyMusatkin@users.noreply.github.com> Date: Tue, 30 Jul 2024 08:32:46 -0700 Subject: [PATCH] Add no copy api variants to json interface (#1138) --- include/aws/common/json.h | 69 ++++++++++++++++++++++++++++- source/json.c | 91 +++++++++++++++++++++------------------ tests/json_test.c | 19 +++++--- 3 files changed, 130 insertions(+), 49 deletions(-) diff --git a/include/aws/common/json.h b/include/aws/common/json.h index b8c4e6cfe..cfda4cf18 100644 --- a/include/aws/common/json.h +++ b/include/aws/common/json.h @@ -23,13 +23,26 @@ AWS_EXTERN_C_BEGIN * * Note: You will need to free the memory for the aws_json_value using aws_json_destroy on the aws_json_value or * on the object/array containing the aws_json_value. - * @param string A byte pointer to the string you want to store in the aws_json_value + * Note: might be slower than c_str version due to internal copy + * @param string A byte cursor you want to store in the aws_json_value * @param allocator The allocator to use when creating the value * @return A new string aws_json_value */ AWS_COMMON_API struct aws_json_value *aws_json_value_new_string(struct aws_allocator *allocator, struct aws_byte_cursor string); +/** + * Creates a new string aws_json_value with the given string and returns a pointer to it. + * + * Note: You will need to free the memory for the aws_json_value using aws_json_destroy on the aws_json_value or + * on the object/array containing the aws_json_value. + * @param string c string pointer you want to store in the aws_json_value + * @param allocator The allocator to use when creating the value + * @return A new string aws_json_value + */ +AWS_COMMON_API +struct aws_json_value *aws_json_value_new_string_from_c_str(struct aws_allocator *allocator, const char *string); + /** * Creates a new number aws_json_value with the given number and returns a pointer to it. * @@ -129,6 +142,7 @@ int aws_json_value_get_boolean(const struct aws_json_value *value, bool *output) * * Note that the aws_json_value will be destroyed when the aws_json_value object is destroyed * by calling "aws_json_destroy()" + * Note: might be slower than c_str version due to internal copy * @param object The object aws_json_value you want to add a value to. * @param key The key to add the aws_json_value at. * @param value The aws_json_value you want to add. @@ -142,8 +156,24 @@ int aws_json_value_add_to_object( struct aws_byte_cursor key, struct aws_json_value *value); +/** + * Adds a aws_json_value to a object aws_json_value. + * + * Note that the aws_json_value will be destroyed when the aws_json_value object is destroyed + * by calling "aws_json_destroy()" + * @param object The object aws_json_value you want to add a value to. + * @param key The key to add the aws_json_value at. + * @param value The aws_json_value you want to add. + * @return AWS_OP_SUCCESS if adding was successful. + * Will return AWS_OP_ERROR if the object passed is invalid or if the passed key + * is already in use in the object. + */ +AWS_COMMON_API +int aws_json_value_add_to_object_c_str(struct aws_json_value *object, const char *key, struct aws_json_value *value); + /** * Returns the aws_json_value at the given key. + * Note: might be slower than c_str version due to internal copy * @param object The object aws_json_value you want to get the value from. * @param key The key that the aws_json_value is at. Is case sensitive. * @return The aws_json_value at the given key, otherwise NULL. @@ -151,8 +181,20 @@ int aws_json_value_add_to_object( AWS_COMMON_API struct aws_json_value *aws_json_value_get_from_object(const struct aws_json_value *object, struct aws_byte_cursor key); +/** + * Returns the aws_json_value at the given key. + * Note: same as aws_json_value_get_from_object but with key as const char *. + * Prefer this method is you have a key thats already a valid char * as it is likely to be faster. + * @param object The object aws_json_value you want to get the value from. + * @param key The key that the aws_json_value is at. Is case sensitive. + * @return The aws_json_value at the given key, otherwise NULL. + */ +AWS_COMMON_API +struct aws_json_value *aws_json_value_get_from_object_c_str(const struct aws_json_value *object, const char *key); + /** * Checks if there is a aws_json_value at the given key. + * Note: might be slower than c_str version due to internal copy * @param object The value aws_json_value you want to check a key in. * @param key The key that you want to check. Is case sensitive. * @return True if a aws_json_value is found. @@ -160,8 +202,20 @@ struct aws_json_value *aws_json_value_get_from_object(const struct aws_json_valu AWS_COMMON_API bool aws_json_value_has_key(const struct aws_json_value *object, struct aws_byte_cursor key); +/** + * Checks if there is a aws_json_value at the given key. + * Note: same as aws_json_value_has_key but with key as const char *. + * Prefer this method is you have a key thats already a valid char * as it is likely to be faster. + * @param object The value aws_json_value you want to check a key in. + * @param key The key that you want to check. Is case sensitive. + * @return True if a aws_json_value is found. + */ +AWS_COMMON_API +bool aws_json_value_has_key_c_str(const struct aws_json_value *object, const char *key); + /** * Removes the aws_json_value at the given key. + * Note: might be slower than c_str version due to internal copy * @param object The object aws_json_value you want to remove a aws_json_value in. * @param key The key that the aws_json_value is at. Is case sensitive. * @return AWS_OP_SUCCESS if the aws_json_value was removed. @@ -171,6 +225,19 @@ bool aws_json_value_has_key(const struct aws_json_value *object, struct aws_byte AWS_COMMON_API int aws_json_value_remove_from_object(struct aws_json_value *object, struct aws_byte_cursor key); +/** + * Removes the aws_json_value at the given key. + * Note: same as aws_json_value_remove_from_object but with key as const char *. + * Prefer this method is you have a key thats already a valid char * as it is likely to be faster. + * @param object The object aws_json_value you want to remove a aws_json_value in. + * @param key The key that the aws_json_value is at. Is case sensitive. + * @return AWS_OP_SUCCESS if the aws_json_value was removed. + * Will return AWS_OP_ERR if the object passed is invalid or if the value + * at the key cannot be found. + */ +AWS_COMMON_API +int aws_json_value_remove_from_object_c_str(struct aws_json_value *object, const char *key); + /** * @brief callback for iterating members of an object * Iteration can be controlled as follows: diff --git a/source/json.c b/source/json.c index 28524a88c..2f1630ea2 100644 --- a/source/json.c +++ b/source/json.c @@ -21,6 +21,12 @@ struct aws_json_value *aws_json_value_new_string(struct aws_allocator *allocator return ret_val; } +struct aws_json_value *aws_json_value_new_string_from_c_str(struct aws_allocator *allocator, const char *string) { + (void)allocator; /* No need for allocator. It is overriden through hooks. */ + void *ret_val = cJSON_CreateString(string); + return ret_val; +} + struct aws_json_value *aws_json_value_new_number(struct aws_allocator *allocator, double number) { (void)allocator; // prevent warnings over unused parameter return (void *)cJSON_CreateNumber(number); @@ -78,92 +84,95 @@ int aws_json_value_add_to_object( struct aws_byte_cursor key, struct aws_json_value *value) { - int result = AWS_OP_ERR; struct aws_string *tmp = aws_string_new_from_cursor(s_aws_json_module_allocator, &key); + int result = aws_json_value_add_to_object_c_str(object, aws_string_c_str(tmp), value); + + aws_string_destroy_secure(tmp); + return result; +} + +int aws_json_value_add_to_object_c_str(struct aws_json_value *object, const char *key, struct aws_json_value *value) { struct cJSON *cjson = (struct cJSON *)object; if (!cJSON_IsObject(cjson)) { - aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); - goto done; + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); } struct cJSON *cjson_value = (struct cJSON *)value; if (cJSON_IsInvalid(cjson_value)) { - result = aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); - goto done; + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); } - if (cJSON_HasObjectItem(cjson, aws_string_c_str(tmp))) { - goto done; + if (cJSON_HasObjectItem(cjson, key)) { + return AWS_OP_ERR; } - cJSON_AddItemToObject(cjson, aws_string_c_str(tmp), cjson_value); - result = AWS_OP_SUCCESS; - -done: - aws_string_destroy_secure(tmp); - return result; + cJSON_AddItemToObject(cjson, key, cjson_value); + return AWS_OP_SUCCESS; } struct aws_json_value *aws_json_value_get_from_object(const struct aws_json_value *object, struct aws_byte_cursor key) { - void *return_value = NULL; struct aws_string *tmp = aws_string_new_from_cursor(s_aws_json_module_allocator, &key); + void *return_value = aws_json_value_get_from_object_c_str(object, aws_string_c_str(tmp)); + aws_string_destroy_secure(tmp); + return return_value; +} + +struct aws_json_value *aws_json_value_get_from_object_c_str(const struct aws_json_value *object, const char *key) { const struct cJSON *cjson = (const struct cJSON *)object; if (!cJSON_IsObject(cjson)) { aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); - goto done; + return NULL; } - if (!cJSON_HasObjectItem(cjson, aws_string_c_str(tmp))) { - goto done; + if (!cJSON_HasObjectItem(cjson, key)) { + return NULL; } - return_value = (void *)cJSON_GetObjectItem(cjson, aws_string_c_str(tmp)); - -done: - aws_string_destroy_secure(tmp); - return return_value; + return (void *)cJSON_GetObjectItem(cjson, key); } bool aws_json_value_has_key(const struct aws_json_value *object, struct aws_byte_cursor key) { struct aws_string *tmp = aws_string_new_from_cursor(s_aws_json_module_allocator, &key); - bool result = false; + bool result = aws_json_value_has_key_c_str(object, aws_string_c_str(tmp)); + + aws_string_destroy_secure(tmp); + return result; +} +bool aws_json_value_has_key_c_str(const struct aws_json_value *object, const char *key) { const struct cJSON *cjson = (const struct cJSON *)object; if (!cJSON_IsObject(cjson)) { - goto done; + return false; } - if (!cJSON_HasObjectItem(cjson, aws_string_c_str(tmp))) { - goto done; + if (!cJSON_HasObjectItem(cjson, key)) { + return false; } - result = true; -done: - aws_string_destroy_secure(tmp); - return result; + return true; } int aws_json_value_remove_from_object(struct aws_json_value *object, struct aws_byte_cursor key) { - int result = AWS_OP_ERR; struct aws_string *tmp = aws_string_new_from_cursor(s_aws_json_module_allocator, &key); + int result = aws_json_value_remove_from_object_c_str(object, aws_string_c_str(tmp)); + aws_string_destroy_secure(tmp); + return result; +} + +int aws_json_value_remove_from_object_c_str(struct aws_json_value *object, const char *key) { struct cJSON *cjson = (struct cJSON *)object; if (!cJSON_IsObject(cjson)) { - aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); - goto done; + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); } - if (!cJSON_HasObjectItem(cjson, aws_string_c_str(tmp))) { - goto done; + if (!cJSON_HasObjectItem(cjson, key)) { + return AWS_OP_ERR; } - cJSON_DeleteItemFromObject(cjson, aws_string_c_str(tmp)); - result = AWS_OP_SUCCESS; - -done: - aws_string_destroy_secure(tmp); - return result; + cJSON_DeleteItemFromObject(cjson, key); + return AWS_OP_SUCCESS; } int aws_json_const_iterate_object( diff --git a/tests/json_test.c b/tests/json_test.c index 8ba2a1c07..ed10fbee6 100644 --- a/tests/json_test.c +++ b/tests/json_test.c @@ -114,6 +114,7 @@ static int s_test_json_parse_from_string(struct aws_allocator *allocator, void * // Testing valid array struct aws_json_value *array_node = aws_json_value_get_from_object(root, aws_byte_cursor_from_c_str("array")); + ASSERT_PTR_EQUALS(array_node, aws_json_value_get_from_object_c_str(root, "array")); ASSERT_NOT_NULL(array_node); ASSERT_TRUE(aws_json_value_is_array(array_node)); ASSERT_TRUE(aws_json_get_array_size(array_node) == 3); @@ -163,12 +164,14 @@ static int s_test_json_parse_from_string(struct aws_allocator *allocator, void * aws_string_destroy_secure(tmp_str); // Testing valid number - struct aws_json_value *number_node = aws_json_value_get_from_object(root, aws_byte_cursor_from_c_str("number")); + struct aws_json_value *number_node = aws_json_value_get_from_object_c_str(root, "number"); ASSERT_NOT_NULL(number_node); ASSERT_TRUE(aws_json_value_is_number(number_node)); double double_test_two = 0; aws_json_value_get_number(number_node, &double_test_two); ASSERT_TRUE(double_test_two == (double)123); + ASSERT_TRUE(aws_json_value_has_key_c_str(root, "number")); + ASSERT_TRUE(aws_json_value_has_key(root, aws_byte_cursor_from_c_str("number"))); // Testing valid object struct aws_json_value *object_node = aws_json_value_get_from_object(root, aws_byte_cursor_from_c_str("object")); @@ -210,6 +213,12 @@ static int s_test_json_parse_from_string(struct aws_allocator *allocator, void * // Test getting invalid type of data ASSERT_INT_EQUALS(aws_json_value_get_number(string_node, NULL), AWS_OP_ERR); + ASSERT_SUCCESS(aws_json_value_remove_from_object(root, aws_byte_cursor_from_c_str("number"))); + ASSERT_FALSE(aws_json_value_has_key_c_str(root, "number")); + + ASSERT_SUCCESS(aws_json_value_remove_from_object_c_str(root, "object")); + ASSERT_FALSE(aws_json_value_has_key_c_str(root, "object")); + aws_json_value_destroy(root); // Make sure that destroying NULL does not have any bad effects. @@ -233,12 +242,8 @@ static int s_test_json_parse_to_string(struct aws_allocator *allocator, void *ct aws_json_value_add_array_element(array, aws_json_value_new_number(allocator, 3)); aws_json_value_add_to_object(root, aws_byte_cursor_from_c_str("array"), array); - aws_json_value_add_to_object( - root, aws_byte_cursor_from_c_str("boolean"), aws_json_value_new_boolean(allocator, true)); - aws_json_value_add_to_object( - root, - aws_byte_cursor_from_c_str("color"), - aws_json_value_new_string(allocator, aws_byte_cursor_from_c_str("gold"))); + aws_json_value_add_to_object_c_str(root, "boolean", aws_json_value_new_boolean(allocator, true)); + aws_json_value_add_to_object_c_str(root, "color", aws_json_value_new_string_from_c_str(allocator, "gold")); aws_json_value_add_to_object(root, aws_byte_cursor_from_c_str("null"), aws_json_value_new_null(allocator)); aws_json_value_add_to_object(root, aws_byte_cursor_from_c_str("number"), aws_json_value_new_number(allocator, 123));