From 0c61aee5971a67a2fa215134f84a6ec4b61f3c94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20G=C3=A1l?= Date: Wed, 1 Jul 2020 14:50:39 +0200 Subject: [PATCH] Improve the JSON C API (#3943) * Added more details into documentation. * Moved the C unit-test into its own file. * Added extra test cases. * Extended the API reference documentation with doctests. JerryScript-DCO-1.0-Signed-off-by: Peter Gal pgal.usz@partner.samsung.com --- docs/02.API-REFERENCE.md | 79 +++++--- jerry-core/api/jerry.c | 34 ++-- .../builtin-objects/ecma-builtin-helpers.h | 2 +- .../ecma/builtin-objects/ecma-builtin-json.c | 15 +- tests/unit-core/test-api.c | 43 ----- tests/unit-core/test-common.h | 20 +++ tests/unit-core/test-json.c | 170 ++++++++++++++++++ 7 files changed, 280 insertions(+), 83 deletions(-) create mode 100644 tests/unit-core/test-json.c diff --git a/docs/02.API-REFERENCE.md b/docs/02.API-REFERENCE.md index 01eb5aabb..5aee46ac0 100644 --- a/docs/02.API-REFERENCE.md +++ b/docs/02.API-REFERENCE.md @@ -8572,7 +8572,11 @@ jerry_get_typedarray_buffer (jerry_value_t value, **Summary** -Returns the same result as `JSON.parse` ecmascript function. +Parses a JSON string creating a JavaScript value. The behaviour is equivalent with +the "JSON.parse(string)" JS call. + +*Note*: Returned value must be freed with [jerry_release_value](#jerry_release_value) when it +is no longer needed. **Prototype** @@ -8582,24 +8586,38 @@ jerry_json_parse (const jerry_char_t *string_p, jerry_size_t string_size); ``` -- `string_p` - a JSON string -- `string_size` - size of the string +- `string_p` - pointer to a JSON string. +- `string_size` - size of the string. - return - - jerry_value_t containing the same as json.parse() - - jerry_value_t containing error massage + - `jerry_value_t` containing a JavaScript value. + - Error value in case of any parse error. *New in version 2.0*. **Example** +[doctest]: # () + ```c +#include "jerryscript.h" + +int +main (void) { + /* Initialize engine */ + jerry_init (JERRY_INIT_EMPTY); + const jerry_char_t data[] = "{\"name\": \"John\", \"age\": 5}"; - jerry_value_t parsed_json = jerry_json_parse (data, sizeof (data) - 1); + jerry_value_t obj = jerry_json_parse (data, sizeof (data) - 1); - // parsed_json now conatins all data stored in data_in_json + /* "obj" now conatins and object created from the "data" JSON string. */ - jerry_release_value (parsed_json); + jerry_release_value (obj); + + /* Cleanup engine */ + jerry_cleanup (); + + return 0; } ``` @@ -8607,37 +8625,58 @@ jerry_json_parse (const jerry_char_t *string_p, **Summary** -Returns the same value as `JSON.stringify` ecmascript function. +Create a JSON string value from a JavaScript value. The behaviour is equivalent with +the "JSON.stringify(input_value)" JS call. + +*Note*: Returned value must be freed with [jerry_release_value](#jerry_release_value) when it +is no longer needed. **Prototype** ```c jerry_value_t -jerry_json_stringify (const jerry_value_t object_to_stringify); +jerry_json_stringify (const jerry_value_t input_value); ``` -- `object_to_stringify` - a jerry_value_t object to stringify +- `input_value` - a `jerry_value_t` to stringify. - return - - jerry_value_t containing the same as json.stringify() - - jerry_value_t containing error massage + - `jerry_value_t` containing a JSON string. + - Error value in case of any stringification error. *New in version 2.0*. **Example** +[doctest]: # () + ```c +#include "jerryscript.h" + +int +main (void) { + /* Initialize engine */ + jerry_init (JERRY_INIT_EMPTY); + jerry_value_t obj = jerry_create_object (); - jerry_value_t key = jerry_create_string ((const jerry_char_t *) "name"); - jerry_value_t value = jerry_create_string ((const jerry_char_t *) "John"); - jerry_release_value (jerry_set_property (obj, key, value)); + { + jerry_value_t key = jerry_create_string ((const jerry_char_t *) "name"); + jerry_value_t value = jerry_create_string ((const jerry_char_t *) "John"); + jerry_release_value (jerry_set_property (obj, key, value)); + jerry_release_value (key); + jerry_release_value (value); + } + jerry_value_t stringified = jerry_json_stringify (obj); - //stringified now contains a json formated string + /* "stringified" now contains a JSON string */ - jerry_release_value (obj); - jerry_release_value (key); - jerry_release_value (value); jerry_release_value (stringified); + jerry_release_value (obj); + + /* Cleanup engine */ + jerry_cleanup (); + + return 0; } ``` diff --git a/jerry-core/api/jerry.c b/jerry-core/api/jerry.c index 3f6e8c6a9..1320009e9 100644 --- a/jerry-core/api/jerry.c +++ b/jerry-core/api/jerry.c @@ -4332,11 +4332,15 @@ jerry_get_typedarray_buffer (jerry_value_t value, /**< TypedArray to get the arr } /* jerry_get_typedarray_buffer */ /** - * Create an object from JSON + * Parse the given JSON string to create a jerry_value_t. + * + * The behaviour is equivalent with the "JSON.parse(string)" JS call. * * Note: - * The returned value must be freed with jerry_release_value - * @return jerry_value_t from json formated string or an error massage + * The returned value must be freed with jerry_release_value. + * + * @return - jerry_value_t containing a JavaScript value. + * - Error value if there was problems during the parse. */ jerry_value_t jerry_json_parse (const jerry_char_t *string_p, /**< json string */ @@ -4352,7 +4356,7 @@ jerry_json_parse (const jerry_char_t *string_p, /**< json string */ ret_value = jerry_throw (ecma_raise_syntax_error (ECMA_ERR_MSG ("JSON string parse error."))); } - return ret_value; + return jerry_return (ret_value); #else /* !ENABLED (JERRY_BUILTIN_JSON) */ JERRY_UNUSED (string_p); JERRY_UNUSED (string_size); @@ -4362,32 +4366,36 @@ jerry_json_parse (const jerry_char_t *string_p, /**< json string */ } /* jerry_json_parse */ /** - * Create a Json formated string from an object + * Create a JSON string from a JavaScript value. + * + * The behaviour is equivalent with the "JSON.stringify(input_value)" JS call. * * Note: - * The returned value must be freed with jerry_release_value - * @return json formated jerry_value_t or an error massage + * The returned value must be freed with jerry_release_value, + * + * @return - jerry_value_t containing a JSON string. + * - Error value if there was a problem during the stringification. */ jerry_value_t -jerry_json_stringify (const jerry_value_t object_to_stringify) /**< a jerry_object_t to stringify */ +jerry_json_stringify (const jerry_value_t input_value) /**< a value to stringify */ { jerry_assert_api_available (); #if ENABLED (JERRY_BUILTIN_JSON) - ecma_value_t ret_value = ecma_builtin_json_string_from_object (object_to_stringify); - - if (ecma_is_value_error_reference (object_to_stringify)) + if (ecma_is_value_error_reference (input_value)) { return jerry_throw (ecma_raise_type_error (ECMA_ERR_MSG (error_value_msg_p))); } + ecma_value_t ret_value = ecma_builtin_json_stringify_no_opts (input_value); + if (ecma_is_value_undefined (ret_value)) { ret_value = jerry_throw (ecma_raise_syntax_error (ECMA_ERR_MSG ("JSON stringify error."))); } - return ret_value; + return jerry_return (ret_value); #else /* ENABLED (JERRY_BUILTIN_JSON) */ - JERRY_UNUSED (object_to_stringify); + JERRY_UNUSED (input_value); return jerry_throw (ecma_raise_syntax_error (ECMA_ERR_MSG ("The JSON has been disabled."))); #endif /* ENABLED (JERRY_BUILTIN_JSON) */ diff --git a/jerry-core/ecma/builtin-objects/ecma-builtin-helpers.h b/jerry-core/ecma/builtin-objects/ecma-builtin-helpers.h index 339a27c17..484b3af27 100644 --- a/jerry-core/ecma/builtin-objects/ecma-builtin-helpers.h +++ b/jerry-core/ecma/builtin-objects/ecma-builtin-helpers.h @@ -214,7 +214,7 @@ typedef struct ecma_value_t ecma_builtin_json_parse_buffer (const lit_utf8_byte_t * str_start_p, lit_utf8_size_t string_size); -ecma_value_t ecma_builtin_json_string_from_object (const ecma_value_t arg1); +ecma_value_t ecma_builtin_json_stringify_no_opts (const ecma_value_t value); bool ecma_json_has_object_in_stack (ecma_json_occurence_stack_item_t *stack_p, ecma_object_t *object_p); bool ecma_has_string_value_in_collection (ecma_collection_t *collection_p, ecma_string_t *string_p); diff --git a/jerry-core/ecma/builtin-objects/ecma-builtin-json.c b/jerry-core/ecma/builtin-objects/ecma-builtin-json.c index 2d7a5b75d..65ebae52f 100644 --- a/jerry-core/ecma/builtin-objects/ecma-builtin-json.c +++ b/jerry-core/ecma/builtin-objects/ecma-builtin-json.c @@ -1349,13 +1349,16 @@ static ecma_value_t ecma_builtin_json_str_helper (ecma_json_stringify_context_t } /* ecma_builtin_json_str_helper */ /** - * Function to create a json formated string from an object + * Function to create a JSON string from a JS value. * - * @return ecma_value_t containing a json string - * Returned value must be freed with ecma_free_value. + * Note: + * The returned value must be freed with ecma_free_value. + * + * @return - ecma_value_t containing a json string. + * - Error value in case of any errors. */ ecma_value_t -ecma_builtin_json_string_from_object (const ecma_value_t arg1) /**< object argument */ +ecma_builtin_json_stringify_no_opts (const ecma_value_t value) /**< value to stringify */ { ecma_json_stringify_context_t context; context.occurence_stack_last_p = NULL; @@ -1364,12 +1367,12 @@ ecma_builtin_json_string_from_object (const ecma_value_t arg1) /**< object argum context.replacer_function_p = NULL; context.gap_str_p = ecma_get_magic_string (LIT_MAGIC_STRING__EMPTY); - ecma_value_t ret_value = ecma_builtin_json_str_helper (&context, arg1); + ecma_value_t ret_value = ecma_builtin_json_str_helper (&context, value); ecma_deref_ecma_string (context.gap_str_p); ecma_stringbuilder_destroy (&context.indent_builder); return ret_value; -} /*ecma_builtin_json_string_from_object*/ +} /* ecma_builtin_json_stringify_no_opts */ /** * The JSON object's 'stringify' routine diff --git a/tests/unit-core/test-api.c b/tests/unit-core/test-api.c index 290caf409..227f725bb 100644 --- a/tests/unit-core/test-api.c +++ b/tests/unit-core/test-api.c @@ -1015,49 +1015,6 @@ main (void) jerry_release_value (args[1]); - { - /*json parser check*/ - const char data_check[]="John"; - jerry_value_t key = jerry_create_string ((const jerry_char_t *) "name"); - const jerry_char_t data[] = "{\"name\": \"John\", \"age\": 5}"; - jerry_value_t parsed_json = jerry_json_parse (data, sizeof (data) - 1); - jerry_value_t has_prop_js = jerry_has_property (parsed_json, key); - TEST_ASSERT (jerry_get_boolean_value (has_prop_js)); - jerry_release_value (has_prop_js); - jerry_value_t parsed_data = jerry_get_property (parsed_json, key); - TEST_ASSERT (jerry_value_is_string (parsed_data) == true); - jerry_size_t buff_size = jerry_get_string_size (parsed_data); - JERRY_VLA (char, buff, buff_size + 1); - jerry_string_to_char_buffer (parsed_data, (jerry_char_t *) buff, buff_size); - buff[buff_size] = '\0'; - TEST_ASSERT (strcmp (data_check, buff) == false); - jerry_release_value (parsed_json); - jerry_release_value (key); - jerry_release_value (parsed_data); - } - - /*json stringify test*/ - { - jerry_value_t obj = jerry_create_object (); - char check_value[] = "{\"name\":\"John\"}"; - jerry_value_t key = jerry_create_string ((const jerry_char_t *) "name"); - jerry_value_t value = jerry_create_string ((const jerry_char_t *) "John"); - res = jerry_set_property (obj, key, value); - TEST_ASSERT (!jerry_value_is_error (res)); - TEST_ASSERT (jerry_value_is_boolean (res) && jerry_get_boolean_value (res)); - jerry_release_value (res); - jerry_value_t stringified = jerry_json_stringify (obj); - TEST_ASSERT (jerry_value_is_string (stringified)); - jerry_size_t buff_size = jerry_get_string_size (stringified); - JERRY_VLA (char, buff, buff_size + 1); - jerry_string_to_char_buffer (stringified, (jerry_char_t *) buff, buff_size); - buff[buff_size] = '\0'; - TEST_ASSERT (strcmp ((const char *) check_value, (const char *) buff) == 0); - jerry_release_value (stringified); - jerry_release_value (obj); - jerry_release_value (key); - jerry_release_value (value); - } jerry_cleanup (); return 0; diff --git a/tests/unit-core/test-common.h b/tests/unit-core/test-common.h index aaa942d21..1f3a89e11 100644 --- a/tests/unit-core/test-common.h +++ b/tests/unit-core/test-common.h @@ -42,6 +42,26 @@ } \ } while (0) +#define TEST_ASSERT_STR(EXPECTED, RESULT) \ + do \ + { \ + const char* __expected = (const char *) (EXPECTED); \ + const char* __result = (const char *) (RESULT); \ + if (strcmp(__expected, __result) != 0) \ + { \ + jerry_port_log (JERRY_LOG_LEVEL_ERROR, \ + "TEST: String comparison failed at %s(%s):%lu.\n" \ + " Expected: '%s'\n Got: '%s'\n", \ + __FILE__, \ + __func__, \ + (unsigned long) __LINE__, \ + __expected, \ + __result); \ + jerry_port_fatal (ERR_FAILED_INTERNAL_ASSERTION); \ + \ + } \ + } while (0) + /** * Test initialization statement that should be included * at the beginning of main function in every unit test. diff --git a/tests/unit-core/test-json.c b/tests/unit-core/test-json.c new file mode 100644 index 000000000..1175cb88f --- /dev/null +++ b/tests/unit-core/test-json.c @@ -0,0 +1,170 @@ +/* Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "config.h" +#include "jerryscript.h" + +#include "test-common.h" + +static jerry_value_t +custom_to_json (const jerry_value_t func_obj_val, /**< function object */ + const jerry_value_t this_val, /**< this value */ + const jerry_value_t args_p[], /**< arguments list */ + const jerry_length_t args_cnt) /**< arguments length */ +{ + JERRY_UNUSED (func_obj_val); + JERRY_UNUSED (this_val); + JERRY_UNUSED (args_p); + JERRY_UNUSED (args_cnt); + + return jerry_create_error (JERRY_ERROR_URI, (const jerry_char_t *) "Error"); +} /* custom_to_json */ + +int +main (void) +{ + TEST_INIT (); + + jerry_init (JERRY_INIT_EMPTY); + + { + /* JSON.parse check */ + const jerry_char_t data[] = "{\"name\": \"John\", \"age\": 5}"; + jerry_value_t parsed_json = jerry_json_parse (data, sizeof (data) - 1); + + /* Check "name" property values */ + jerry_value_t name_key = jerry_create_string ((const jerry_char_t *) "name"); + + jerry_value_t has_name = jerry_has_property (parsed_json, name_key); + TEST_ASSERT (jerry_get_boolean_value (has_name)); + jerry_release_value (has_name); + + jerry_value_t name_value = jerry_get_property (parsed_json, name_key); + TEST_ASSERT (jerry_value_is_string (name_value) == true); + + jerry_size_t name_size = jerry_get_string_size (name_value); + JERRY_VLA (jerry_char_t, name_data, name_size + 1); + jerry_size_t copied = jerry_string_to_char_buffer (name_value, name_data, name_size); + name_data[name_size] = '\0'; + + jerry_release_value (name_value); + + TEST_ASSERT (copied == name_size); + TEST_ASSERT_STR ("John", name_data); + jerry_release_value (name_key); + + /* Check "age" property values */ + jerry_value_t age_key = jerry_create_string ((const jerry_char_t *) "age"); + + jerry_value_t has_age = jerry_has_property (parsed_json, age_key); + TEST_ASSERT (jerry_get_boolean_value (has_age)); + jerry_release_value (has_age); + + jerry_value_t age_value = jerry_get_property (parsed_json, age_key); + TEST_ASSERT (jerry_value_is_number (age_value) == true); + TEST_ASSERT (jerry_get_number_value (age_value) == 5.0); + + jerry_release_value (age_value); + jerry_release_value (age_key); + + jerry_release_value (parsed_json); + } + + /* JSON.parse error checks */ + { + jerry_value_t parsed_json = jerry_json_parse ((const jerry_char_t *) "", 0); + TEST_ASSERT (jerry_value_is_error (parsed_json)); + TEST_ASSERT (jerry_get_error_type (parsed_json) == JERRY_ERROR_SYNTAX); + jerry_release_value (parsed_json); + } + + { + jerry_value_t parsed_json = jerry_json_parse ((const jerry_char_t *) "-", 1); + TEST_ASSERT (jerry_value_is_error (parsed_json)); + TEST_ASSERT (jerry_get_error_type (parsed_json) == JERRY_ERROR_SYNTAX); + jerry_release_value (parsed_json); + } + + /* JSON.stringify check */ + { + jerry_value_t obj = jerry_create_object (); + /* Fill "obj" with data */ + { + jerry_value_t name_key = jerry_create_string ((const jerry_char_t *) "name"); + jerry_value_t name_value = jerry_create_string ((const jerry_char_t *) "John"); + jerry_value_t name_set = jerry_set_property (obj, name_key, name_value); + TEST_ASSERT (!jerry_value_is_error (name_set)); + TEST_ASSERT (jerry_value_is_boolean (name_set)); + TEST_ASSERT (jerry_get_boolean_value (name_set)); + jerry_release_value (name_key); + jerry_release_value (name_value); + jerry_release_value (name_set); + } + { + jerry_value_t age_key = jerry_create_string ((const jerry_char_t *) "age"); + jerry_value_t age_value = jerry_create_number (32); + jerry_value_t age_set = jerry_set_property (obj, age_key, age_value); + TEST_ASSERT (!jerry_value_is_error (age_set)); + TEST_ASSERT (jerry_value_is_boolean (age_set)); + TEST_ASSERT (jerry_get_boolean_value (age_set)); + jerry_release_value (age_key); + jerry_release_value (age_value); + jerry_release_value (age_set); + } + + jerry_value_t json_string = jerry_json_stringify (obj); + TEST_ASSERT (jerry_value_is_string (json_string)); + + jerry_release_value (obj); + + jerry_size_t json_size = jerry_get_string_size (json_string); + JERRY_VLA (jerry_char_t, json_data, json_size + 1); + jerry_string_to_char_buffer (json_string, json_data, json_size); + json_data[json_size] = '\0'; + + const char check_value[] = "{\"name\":\"John\",\"age\":32}"; + TEST_ASSERT_STR (check_value, json_data); + + jerry_release_value (json_string); + } + + /* Custom "toJSON" invocation test */ + { + jerry_value_t obj = jerry_create_object (); + /* Fill "obj" with data */ + { + jerry_value_t name_key = jerry_create_string ((const jerry_char_t *) "toJSON"); + jerry_value_t name_value = jerry_create_external_function (custom_to_json); + jerry_value_t name_set = jerry_set_property (obj, name_key, name_value); + TEST_ASSERT (!jerry_value_is_error (name_set)); + TEST_ASSERT (jerry_value_is_boolean (name_set)); + TEST_ASSERT (jerry_get_boolean_value (name_set)); + jerry_release_value (name_key); + jerry_release_value (name_value); + jerry_release_value (name_set); + } + + jerry_value_t json_string = jerry_json_stringify (obj); + TEST_ASSERT (jerry_value_is_error (json_string)); + TEST_ASSERT (jerry_get_error_type (json_string) == JERRY_ERROR_URI); + + jerry_release_value (json_string); + jerry_release_value (obj); + } + + jerry_cleanup (); + + return 0; +} /* main */