# HG changeset patch # User Mike Becker # Date 1765490395 -3600 # Node ID cd2e974410ad8362392f786af3cc59d7ab797fb1 # Parent 0499bf03aef367313acfb8763c39830708fde28a add cxJsonToString() and cxJsonToPrettyString() - resolves #778 diff -r 0499bf03aef3 -r cd2e974410ad CHANGELOG --- 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 diff -r 0499bf03aef3 -r cd2e974410ad docs/Writerside/topics/about.md --- 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 diff -r 0499bf03aef3 -r cd2e974410ad docs/Writerside/topics/json.h.md --- 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 | |-------------------|-----------------|----------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------| diff -r 0499bf03aef3 -r cd2e974410ad src/cx/json.h --- 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. * diff -r 0499bf03aef3 -r cd2e974410ad src/json.c --- 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); +} diff -r 0499bf03aef3 -r cd2e974410ad tests/test_json.c --- 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; }