Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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;
```
Expand Down Expand Up @@ -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`.
Expand Down
43 changes: 35 additions & 8 deletions cJSON.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}

Expand Down Expand Up @@ -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);
Expand Down
3 changes: 3 additions & 0 deletions cJSON.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion cJSON_Utils.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
40 changes: 40 additions & 0 deletions tests/cjson_add.c
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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);

Expand Down
4 changes: 2 additions & 2 deletions tests/misc_tests.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down
39 changes: 39 additions & 0 deletions tests/readme_examples.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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\
Expand All @@ -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();
Expand Down