diff --git a/cJSON.c b/cJSON.c index 88c2d95b3..1c0e2ee15 100644 --- a/cJSON.c +++ b/cJSON.c @@ -619,14 +619,20 @@ static cJSON_bool print_number(const cJSON * const item, printbuffer * const out } else { - /* Try 15 decimal places of precision to avoid nonsignificant nonzero digits */ - length = sprintf((char*)number_buffer, "%1.15g", d); - - /* Check whether the original double can be recovered */ - if ((sscanf((char*)number_buffer, "%lg", &test) != 1) || !compare_double((double)test, d)) - { - /* If not, print with 17 decimal places of precision */ - length = sprintf((char*)number_buffer, "%1.17g", d); + /* If decimal places are present and value is not an integer type */ + if (item->decimal_places && (ceil(d) != d)) { + /* Then convert it to string */ + length = sprintf((char*) number_buffer, "%.*f", item->decimal_places, d); + } else { + /* Try 15 decimal places of precision to avoid nonsignificant nonzero digits */ + length = sprintf((char*)number_buffer, "%1.15g", d); + + /* Check whether the original double can be recovered */ + if ((sscanf((char*)number_buffer, "%lg", &test) != 1) || !compare_double((double)test, d)) + { + /* If not, print with 17 decimal places of precision */ + length = sprintf((char*)number_buffer, "%1.17g", d); + } } } @@ -2207,6 +2213,27 @@ CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * return NULL; } +CJSON_PUBLIC(cJSON*) cJSON_AddNumberWithPrecisionToObject(cJSON * const object, const char * const name, const double n, int decimal_places) +{ + cJSON *number_item; + + if (decimal_places <= 0 || decimal_places > 14) + { + return NULL; + } + + number_item = cJSON_CreateNumber(n); + + if (add_item_to_object(object, name, number_item, &global_hooks, false)) + { + number_item->decimal_places = decimal_places; + return number_item; + } + + cJSON_Delete(number_item); + return NULL; +} + CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string) { cJSON *string_item = cJSON_CreateString(string); diff --git a/cJSON.h b/cJSON.h index cab5feb42..0ab281dc1 100644 --- a/cJSON.h +++ b/cJSON.h @@ -117,6 +117,8 @@ typedef struct cJSON int valueint; /* The item's number, if type==cJSON_Number */ double valuedouble; + /* Decimal places */ + int decimal_places; /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */ char *string; @@ -272,6 +274,7 @@ CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * co CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name); CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean); CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number); +CJSON_PUBLIC(cJSON*) cJSON_AddNumberWithPrecisionToObject(cJSON * const object, const char * const name, const double n, int decimal_places); CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string); CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw); CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name); diff --git a/cJSON_Utils.c b/cJSON_Utils.c index 8b38eb253..17f7859c4 100644 --- a/cJSON_Utils.c +++ b/cJSON_Utils.c @@ -840,7 +840,7 @@ static int apply_patch(cJSON *object, const cJSON *patch, const cJSON_bool case_ { if (opcode == REMOVE) { - static const cJSON invalid = { NULL, NULL, NULL, cJSON_Invalid, NULL, 0, 0, NULL}; + static const cJSON invalid = { NULL, NULL, NULL, cJSON_Invalid, NULL, 0, 0, 0, NULL}; overwrite_item(object, invalid); diff --git a/tests/cjson_add.c b/tests/cjson_add.c index ac96ce75d..cdaa52887 100644 --- a/tests/cjson_add.c +++ b/tests/cjson_add.c @@ -255,6 +255,43 @@ static void cjson_add_number_should_add_number(void) cJSON_Delete(root); } +static void assert_add_number_with_precision_prints(const char *expected, double number, int decimal_places) +{ + cJSON *root = cJSON_CreateObject(); + char *printed = NULL; + + TEST_ASSERT_NOT_NULL(cJSON_AddNumberWithPrecisionToObject(root, "number", number, decimal_places)); + + printed = cJSON_PrintUnformatted(root); + TEST_ASSERT_EQUAL_STRING(expected, printed); + + cJSON_free(printed); + cJSON_Delete(root); +} + +static void cjson_add_number_with_precision_should_format_output(void) +{ + assert_add_number_with_precision_prints("{\"number\":1.20}", 1.2, 2); + assert_add_number_with_precision_prints("{\"number\":1.24}", 1.236, 2); + assert_add_number_with_precision_prints("{\"number\":-1.200}", -1.2, 3); + assert_add_number_with_precision_prints("{\"number\":0.12345678901234}", 0.12345678901234, 14); +} + +static void cjson_add_number_with_precision_should_reject_invalid_precision(void) +{ + cJSON *root = cJSON_CreateObject(); + char *printed = NULL; + + TEST_ASSERT_NULL(cJSON_AddNumberWithPrecisionToObject(root, "number", 1.2, 0)); + TEST_ASSERT_NULL(cJSON_AddNumberWithPrecisionToObject(root, "number", 1.2, 15)); + + printed = cJSON_PrintUnformatted(root); + TEST_ASSERT_EQUAL_STRING("{}", printed); + + cJSON_free(printed); + cJSON_Delete(root); +} + static void cjson_add_number_should_fail_with_null_pointers(void) { cJSON *root = cJSON_CreateObject(); @@ -448,6 +485,8 @@ int CJSON_CDECL main(void) RUN_TEST(cjson_add_bool_should_fail_on_allocation_failure); RUN_TEST(cjson_add_number_should_add_number); + RUN_TEST(cjson_add_number_with_precision_should_format_output); + RUN_TEST(cjson_add_number_with_precision_should_reject_invalid_precision); RUN_TEST(cjson_add_number_should_fail_with_null_pointers); RUN_TEST(cjson_add_number_should_fail_on_allocation_failure); diff --git a/tests/misc_tests.c b/tests/misc_tests.c index fe2325e96..664462895 100644 --- a/tests/misc_tests.c +++ b/tests/misc_tests.c @@ -239,7 +239,7 @@ static void cjson_should_not_follow_too_deep_circular_references(void) static void cjson_set_number_value_should_set_numbers(void) { - cJSON number[1] = {{NULL, NULL, NULL, cJSON_Number, NULL, 0, 0, NULL}}; + cJSON number[1] = {{NULL, NULL, NULL, cJSON_Number, NULL, 0, 0, 0, NULL}}; cJSON_SetNumberValue(number, 1.5); TEST_ASSERT_EQUAL(1, number->valueint); @@ -361,7 +361,7 @@ static void cjson_replace_item_via_pointer_should_replace_items(void) static void cjson_replace_item_in_object_should_preserve_name(void) { - cJSON root[1] = {{NULL, NULL, NULL, 0, NULL, 0, 0, NULL}}; + cJSON root[1] = {{NULL, NULL, NULL, 0, NULL, 0, 0, 0, NULL}}; cJSON *child = NULL; cJSON *replacement = NULL; cJSON_bool flag = false;