add cxJsonToString() and cxJsonToPrettyString() - resolves #778 default tip

Thu, 11 Dec 2025 22:59:55 +0100

author
Mike Becker <universe@uap-core.de>
date
Thu, 11 Dec 2025 22:59:55 +0100
changeset 1573
cd2e974410ad
parent 1572
0499bf03aef3

add cxJsonToString() and cxJsonToPrettyString() - resolves #778

CHANGELOG file | annotate | diff | comparison | revisions
docs/Writerside/topics/about.md file | annotate | diff | comparison | revisions
docs/Writerside/topics/json.h.md file | annotate | diff | comparison | revisions
src/cx/json.h file | annotate | diff | comparison | revisions
src/json.c file | annotate | diff | comparison | revisions
tests/test_json.c file | annotate | diff | comparison | revisions
--- a/CHANGELOG	Thu Dec 11 22:43:13 2025 +0100
+++ b/CHANGELOG	Thu Dec 11 22:59:55 2025 +0100
@@ -2,7 +2,7 @@
 ------------------------
 
  * adds cx_system_page_size() to allocator.h
- * adds cxJsonFromString()
+ * adds cxJsonFromString(), cxJsonToString(), and cxJsonToPrettyString()
  * adds line continuation support to CxProperties / CxPropertiesConfig
  * adds cxBufferMaximumCapacity()
  * adds CX_BUFFER_DO_NOT_FREE buffer flag
--- a/docs/Writerside/topics/about.md	Thu Dec 11 22:43:13 2025 +0100
+++ b/docs/Writerside/topics/about.md	Thu Dec 11 22:59:55 2025 +0100
@@ -29,7 +29,7 @@
 ### Version 4.0 - preview {collapsible="true"}
 
 * adds cx_system_page_size() to allocator.h
-* adds cxJsonFromString()
+* adds cxJsonFromString(), cxJsonToString(), and cxJsonToPrettyString()
 * adds line continuation support to CxProperties / CxPropertiesConfig
 * adds cxBufferMaximumCapacity()
 * adds CX_BUFFER_DO_NOT_FREE buffer flag
--- a/docs/Writerside/topics/json.h.md	Thu Dec 11 22:43:13 2025 +0100
+++ b/docs/Writerside/topics/json.h.md	Thu Dec 11 22:59:55 2025 +0100
@@ -346,6 +346,12 @@
 
 int cxJsonWrite(void* target, const CxJsonValue* value,
         cx_write_func wfunc, const CxJsonWriter* settings);
+
+cxmutstr cxJsonToString(CxJsonValue *value,
+        const CxAllocator *allocator);
+
+cxmutstr cxJsonToPrettyString(CxJsonValue *value,
+        const CxAllocator *allocator);
 ```
 
 A JSON value can be formatted with the `cxJsonWrite()` function.
@@ -353,7 +359,9 @@
 The `target` can be a stream, a UCX [buffer](buffer.h.md), or anything else that can be written to with a write function.
 The behavior of the function is controlled via a `CxJsonWriter` struct.
 With the functions `cxJsonWriterCompact()` and `cxJsonWriterPretty()` you can create default settings,
-which you may modify to suit your needs.
+which you may modify to suit your needs (see the table below).
+The functions `cxJsonToString()` and `cxJsonToPrettyString()` are convenience functions
+which create a writer with default settings to produce a null-terminated string allocated by the specified allocator.
 
 | Setting           | Compact Default | Pretty Default                   | Description                                                                                                                                                      |
 |-------------------|-----------------|----------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
--- a/src/cx/json.h	Thu Dec 11 22:43:13 2025 +0100
+++ b/src/cx/json.h	Thu Dec 11 22:59:55 2025 +0100
@@ -473,6 +473,33 @@
 CX_EXPORT int cxJsonWrite(void* target, const CxJsonValue* value,
         cx_write_func wfunc, const CxJsonWriter* settings);
 
+
+/**
+ * Produces a compact string representation of the specified JSON value.
+ *
+ * @param value the JSON value
+ * @param allocator the allocator for the string
+ * @return the produced string
+ * @see cxJsonWrite()
+ * @see cxJsonWriterCompact()
+ * @see cxJsonToPrettyString()
+ */
+cx_attr_nonnull_arg(1)
+CX_EXPORT cxmutstr cxJsonToString(CxJsonValue *value, const CxAllocator *allocator);
+
+/**
+ * Produces a pretty string representation of the specified JSON value.
+ *
+ * @param value the JSON value
+ * @param allocator the allocator for the string
+ * @return the produced string
+ * @see cxJsonWrite()
+ * @see cxJsonWriterPretty()
+ * @see cxJsonToString()
+ */
+cx_attr_nonnull_arg(1)
+CX_EXPORT cxmutstr cxJsonToPrettyString(CxJsonValue *value, const CxAllocator *allocator);
+
 /**
  * Initializes the JSON interface.
  *
--- a/src/json.c	Thu Dec 11 22:43:13 2025 +0100
+++ b/src/json.c	Thu Dec 11 22:59:55 2025 +0100
@@ -1409,3 +1409,35 @@
     }
     return cx_json_write_rec(target, value, wfunc, settings, 0);
 }
+
+static cxmutstr cx_json_to_string(CxJsonValue *value, const CxAllocator *allocator, CxJsonWriter *writer) {
+    if (allocator == NULL) allocator = cxDefaultAllocator;
+    CxBuffer buffer;
+    if (cxBufferInit(&buffer, NULL, 128, allocator,
+        CX_BUFFER_AUTO_EXTEND | CX_BUFFER_DO_NOT_FREE)) {
+        return (cxmutstr){NULL, 0};
+    }
+    if (cx_json_write_rec(&buffer, value, cxBufferWriteFunc, writer, 0)) {
+        // LCOV_EXCL_START
+        buffer.flags &= ~CX_BUFFER_DO_NOT_FREE;
+        cxBufferDestroy(&buffer);
+        return (cxmutstr){NULL, 0};
+        // LCOV_EXCL_STOP
+    } else {
+        cxBufferTerminate(&buffer);
+        cxmutstr str = cx_mutstrn(buffer.space, buffer.size);
+        cxBufferDestroy(&buffer);
+        return str;
+    }
+
+}
+
+cxmutstr cxJsonToString(CxJsonValue *value, const CxAllocator *allocator) {
+    CxJsonWriter writer = cxJsonWriterCompact();
+    return cx_json_to_string(value, allocator, &writer);
+}
+
+cxmutstr cxJsonToPrettyString(CxJsonValue *value, const CxAllocator *allocator) {
+    CxJsonWriter writer = cxJsonWriterPretty(true);
+    return cx_json_to_string(value, allocator, &writer);
+}
--- a/tests/test_json.c	Thu Dec 11 22:43:13 2025 +0100
+++ b/tests/test_json.c	Thu Dec 11 22:59:55 2025 +0100
@@ -1249,12 +1249,7 @@
     cx_testing_allocator_destroy(&talloc);
 }
 
-CX_TEST_SUBROUTINE(test_json_write_sub,
-        const CxAllocator *allocator,
-        cxstring expected,
-        const CxJsonWriter *writer
-) {
-    // create the value
+static CxJsonValue *test_json_write_create_test_object(const CxAllocator *allocator) {
     CxJsonValue *obj = cxJsonCreateObj(allocator);
     cxJsonObjPutLiteral(obj, "bool", CX_JSON_FALSE);
     cxJsonObjPutNumber(obj, "int", 47); // purposely use PutNumber to put an int
@@ -1278,6 +1273,16 @@
     cxJsonArrAddValues(ints, &nested_array, 1);
     cxJsonArrAddIntegers(nested_array, (int64_t[]){16, 23}, 2);
     cxJsonArrAddIntegers(ints, (int64_t[]){42}, 1);
+    return obj;
+}
+
+CX_TEST_SUBROUTINE(test_json_write_sub,
+        const CxAllocator *allocator,
+        cxstring expected,
+        const CxJsonWriter *writer
+) {
+    // create the value
+    CxJsonValue *obj = test_json_write_create_test_object(allocator);
 
     // write it to a buffer
     CxBuffer buf;
@@ -1567,6 +1572,82 @@
     cxBufferDestroy(&buf);
 }
 
+CX_TEST(test_json_to_string) {
+    CxTestingAllocator talloc;
+    cx_testing_allocator_init(&talloc);
+    CxAllocator *allocator = &talloc.base;
+    CX_TEST_DO {
+        // expected value
+        cxstring expected = cx_str(
+"{\"bool\":false,"
+"\"int\":47,"
+"\"strings\":[\"hello\",\"world\"],"
+"\"nested\":{"
+"\"objects\":[{"
+"\"name1\":1,"
+"\"name2\":3"
+"},{"
+"\"name2\":7,"
+"\"name1\":3"
+"}],"
+"\"floats\":[3.1415,47.11,8.15],"
+"\"literals\":[true,null,false],"
+"\"ints\":[4,8,15,[16,23],42]"
+"}"
+"}"
+        );
+
+        CxJsonValue *obj = test_json_write_create_test_object(allocator);
+        cxmutstr result = cxJsonToString(obj, allocator);
+        CX_TEST_ASSERT(0 == cx_strcmp(result, expected));
+        CX_TEST_ASSERT(result.ptr[result.length] == '\0');
+
+        cx_strfree_a(allocator, &result);
+        cxJsonValueFree(obj);
+
+        CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
+    }
+    cx_testing_allocator_destroy(&talloc);
+}
+
+CX_TEST(test_json_to_pretty_string) {
+    CxTestingAllocator talloc;
+    cx_testing_allocator_init(&talloc);
+    CxAllocator *allocator = &talloc.base;
+    CX_TEST_DO {
+        cxstring expected = cx_str(
+"{\n"
+"    \"bool\": false,\n"
+"    \"int\": 47,\n"
+"    \"strings\": [\"hello\", \"world\"],\n"
+"    \"nested\": {\n"
+"        \"objects\": [{\n"
+"            \"name1\": 1,\n"
+"            \"name2\": 3\n"
+"        }, {\n"
+"            \"name2\": 7,\n"
+"            \"name1\": 3\n"
+"        }],\n"
+"        \"floats\": [3.1415, 47.11, 8.15],\n"
+"        \"literals\": [true, null, false],\n"
+"        \"ints\": [4, 8, 15, [16, 23], 42]\n"
+"    }\n"
+"}"
+        );
+
+        CxJsonValue *obj = test_json_write_create_test_object(allocator);
+        cxmutstr result = cxJsonToPrettyString(obj, allocator);
+        CX_TEST_ASSERT(0 == cx_strcmp(result, expected));
+        CX_TEST_ASSERT(result.ptr[result.length] == '\0');
+
+        cx_strfree_a(allocator, &result);
+        cxJsonValueFree(obj);
+
+        CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
+    }
+    cx_testing_allocator_destroy(&talloc);
+}
+
 CxTestSuite *cx_test_suite_json(void) {
     CxTestSuite *suite = cx_test_suite_new("json");
 
@@ -1609,6 +1690,8 @@
     cx_test_register(suite, test_json_write_name_escape);
     cx_test_register(suite, test_json_write_solidus);
     cx_test_register(suite, test_json_write_nothing);
+    cx_test_register(suite, test_json_to_string);
+    cx_test_register(suite, test_json_to_pretty_string);
     
     return suite;
 }

mercurial