From 5a0eacbb0314e0744b348c417f77660a66385e69 Mon Sep 17 00:00:00 2001 From: Ajay Bhargav Date: Mon, 3 Feb 2020 20:34:53 +0530 Subject: [PATCH 1/5] Add API for formatting fractional decimal places Add cJSON_AddNumberWithPrecisionToObject so non-integral values can be printed with a requested number of digits after the decimal point. Integral values retain normal JSON formatting without a decimal point or trailing zeroes. Signed-off-by: Ajay Bhargav --- cJSON.c | 43 +++++++++++++++++++++++++++++++++++-------- cJSON.h | 3 +++ 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/cJSON.c b/cJSON.c index 88c2d95b3..70715fb39 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); + /* Apply decimal-place formatting only when the value has a fractional part. */ + 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..d8b0688e1 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 used when printing non-integral values; 0 uses default formatting. */ + 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); From bda1e803e06653c5089940025ec65486f5a02349 Mon Sep 17 00:00:00 2001 From: Ajay Bhargav Date: Tue, 23 Feb 2021 17:01:14 +0530 Subject: [PATCH 2/5] Update cJSON_Utils for structural changes Signed-off-by: Ajay Bhargav --- cJSON_Utils.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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); From b640b635017db33e806a6166716f6280474bd1fa Mon Sep 17 00:00:00 2001 From: Ajay Bhargav Date: Tue, 23 Feb 2021 17:07:43 +0530 Subject: [PATCH 3/5] tests:Update misc_tests for cJSON structure change Signed-off-by: Ajay Bhargav --- tests/misc_tests.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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; From b0097b07dbf75bee862f50e2bfca270c1c96e65b Mon Sep 17 00:00:00 2001 From: Chris Morgan Date: Fri, 3 Jul 2026 07:55:30 -0400 Subject: [PATCH 4/5] test: cover AddNumberWithPrecision output Add public API regressions for cJSON_AddNumberWithPrecisionToObject formatting through cJSON_PrintUnformatted. The cases assert requested decimal-place formatting for fractional values, printf-style rounding, negative number formatting, preservation of ordinary integer formatting without a decimal point or trailing zeroes, and the accepted maximum precision boundary. Also cover invalid precision bounds by checking that 0 and 15 are rejected and leave the destination object empty. --- tests/cjson_add.c | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/tests/cjson_add.c b/tests/cjson_add.c index ac96ce75d..f99d5cfda 100644 --- a/tests/cjson_add.c +++ b/tests/cjson_add.c @@ -255,6 +255,44 @@ 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\":3}", 3.0, 5); + 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 +486,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); From 96759d8aec11684f02e02789a8e97b171d213a02 Mon Sep 17 00:00:00 2001 From: Chris Morgan Date: Sat, 1 Aug 2026 14:04:06 -0400 Subject: [PATCH 5/5] docs: add AddNumberWithPrecision example Document rounding a high-precision fractional value and unchanged integral formatting, including the generated JSON. Mirror the example in readme_examples so the documented behavior remains executable. --- README.md | 40 ++++++++++++++++++++++++++++++++++++++++ tests/readme_examples.c | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) diff --git a/README.md b/README.md index 9c7ed142c..9f56731e5 100644 --- a/README.md +++ b/README.md @@ -200,6 +200,8 @@ typedef struct cJSON /* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */ int valueint; double valuedouble; + /* Decimal places used when printing non-integral values; 0 uses default formatting. */ + int decimal_places; char *string; } cJSON; ``` @@ -279,6 +281,44 @@ To iterate over an object, you can use the `cJSON_ArrayForEach` macro the same w cJSON also provides convenient helper functions for quickly creating a new item and adding it to an object, like `cJSON_AddNullToObject`. They return a pointer to the new item or `NULL` if they failed. +`cJSON_AddNumberWithPrecisionToObject` specifies the number of digits after the decimal point when printing a non-integral value. Non-integral values are padded or rounded as needed. Integral values are printed without a decimal point or trailing zeroes. `decimal_places` must be between 1 and 14. For example, the following rounds the representation of pi to six decimal places while leaving an integral value unchanged: + +```c +/* Returns a heap-allocated string that must be released with cJSON_free. */ +char *create_numbers_with_precision(void) +{ + char *string = NULL; + cJSON *object = cJSON_CreateObject(); + + if (object == NULL) + { + return NULL; + } + + if (cJSON_AddNumberWithPrecisionToObject(object, "pi", 3.141592653589793, 6) == NULL) + { + goto end; + } + + if (cJSON_AddNumberWithPrecisionToObject(object, "integer", 3.0, 5) == NULL) + { + goto end; + } + + string = cJSON_PrintUnformatted(object); + +end: + cJSON_Delete(object); + return string; +} +``` + +The generated JSON is: + +```json +{"pi":3.141593,"integer":3} +``` + ### Parsing JSON Given some JSON in a zero terminated string, you can parse it with `cJSON_Parse`. diff --git a/tests/readme_examples.c b/tests/readme_examples.c index 09850cd40..5aaa1c0c5 100644 --- a/tests/readme_examples.c +++ b/tests/readme_examples.c @@ -165,6 +165,34 @@ static char *create_monitor_with_helpers(void) return string; } +/* Returns a heap-allocated string that must be released with cJSON_free. */ +static char *create_numbers_with_precision(void) +{ + char *string = NULL; + cJSON *object = cJSON_CreateObject(); + + if (object == NULL) + { + return NULL; + } + + if (cJSON_AddNumberWithPrecisionToObject(object, "pi", 3.141592653589793, 6) == NULL) + { + goto end; + } + + if (cJSON_AddNumberWithPrecisionToObject(object, "integer", 3.0, 5) == NULL) + { + goto end; + } + + string = cJSON_PrintUnformatted(object); + +end: + cJSON_Delete(object); + return string; +} + /* return 1 if the monitor supports full hd, 0 otherwise */ static int supports_full_hd(const char * const monitor) { @@ -232,6 +260,16 @@ static void create_monitor_with_helpers_should_create_a_monitor(void) free(monitor); } +static void create_numbers_with_precision_should_round_and_preserve_integers(void) +{ + char *numbers = create_numbers_with_precision(); + + TEST_ASSERT_NOT_NULL(numbers); + TEST_ASSERT_EQUAL_STRING("{\"pi\":3.141593,\"integer\":3}", numbers); + + cJSON_free(numbers); +} + static void supports_full_hd_should_check_for_full_hd_support(void) { static const char *monitor_without_hd = "{\n\ @@ -252,6 +290,7 @@ int CJSON_CDECL main(void) RUN_TEST(create_monitor_should_create_a_monitor); RUN_TEST(create_monitor_with_helpers_should_create_a_monitor); + RUN_TEST(create_numbers_with_precision_should_round_and_preserve_integers); RUN_TEST(supports_full_hd_should_check_for_full_hd_support); return UNITY_END();