# HG changeset patch # User Mike Becker # Date 1765132611 -3600 # Node ID afdaa70034f809b2fd3cb9f7b43a1606607db955 # Parent 8972247f54e8e0bf86f028efde6f90969c68e09c add cxJsonFromString() - resolves #771 diff -r 8972247f54e8 -r afdaa70034f8 CHANGELOG --- a/CHANGELOG Sun Dec 07 15:34:46 2025 +0100 +++ b/CHANGELOG Sun Dec 07 19:36:51 2025 +0100 @@ -2,6 +2,7 @@ ------------------------ * adds cx_system_page_size() to allocator.h + * adds cxJsonFromString() * changes cxFreeDefault() from a macro to a function so that it can be used as a simple destructor * changes cxBufferReserve() to allow reducing the capacity * changes the members of CxJson and CxJsonValue diff -r 8972247f54e8 -r afdaa70034f8 docs/Writerside/topics/about.md --- a/docs/Writerside/topics/about.md Sun Dec 07 15:34:46 2025 +0100 +++ b/docs/Writerside/topics/about.md Sun Dec 07 19:36:51 2025 +0100 @@ -29,6 +29,7 @@ ### Version 4.0 - preview {collapsible="true"} * adds cx_system_page_size() to allocator.h +* adds cxJsonFromString() * changes cxFreeDefault() from a macro to a function so that it can be used as a simple destructor * changes cxBufferReserve() to allow reducing the capacity * changes the members of CxJson and CxJsonValue diff -r 8972247f54e8 -r afdaa70034f8 docs/Writerside/topics/json.h.md --- a/docs/Writerside/topics/json.h.md Sun Dec 07 15:34:46 2025 +0100 +++ b/docs/Writerside/topics/json.h.md Sun Dec 07 19:36:51 2025 +0100 @@ -28,6 +28,9 @@ CxJsonStatus cxJsonNext(CxJson *json, CxJsonValue **value); void cxJsonDestroy(CxJson *json); + +CxJsonStatus cxJsonFromString(const CxAllocator *allocator, + AnyStr str, CxJsonValue **value); ``` The first step is to initialize a `CxJson` structure with a call to `cxJsonInit()`, @@ -55,6 +58,8 @@ If you want to reuse a `CxJson` structure, you can call `cxJsonReset()`, even if the last operation was a failure. Otherwise, you need to call `cxJsonDestroy()` when you are done with the parser. +The function `cxJsonFromString()` combines the above procedure. + ### List of Status Codes Below is a full list of status codes for `cxJsonNext()`. diff -r 8972247f54e8 -r afdaa70034f8 src/cx/json.h --- a/src/cx/json.h Sun Dec 07 15:34:46 2025 +0100 +++ b/src/cx/json.h Sun Dec 07 19:36:51 2025 +0100 @@ -557,6 +557,36 @@ */ #define cxJsonFill(json, str) cx_json_fill(json, cx_strcast(str)) + +/** + * Internal function - use cxJsonFromString() instead. + * + * @param allocator the allocator for the JSON value + * @param str the string to parse + * @param value a pointer where the JSON value shall be stored to + * @return status code + */ +cx_attr_nonnull_arg(3) +CX_EXPORT CxJsonStatus cx_json_from_string(const CxAllocator *allocator, + cxstring str, CxJsonValue **value); + +/** + * Parses a string into a JSON value. + * + * @param allocator (@c CxAllocator*) the allocator for the JSON value + * @param str (any string) the string to parse + * @param value (@c CxJsonValue**) a pointer where the JSON value shall be stored to + * @retval CX_JSON_NO_ERROR success + * @retval CX_JSON_NO_DATA the string was empty or blank + * @retval CX_JSON_INCOMPLETE_DATA the string unexpectedly ended + * @retval CX_JSON_BUFFER_ALLOC_FAILED allocating internal buffer space failed + * @retval CX_JSON_VALUE_ALLOC_FAILED allocating memory for the CxJsonValue failed + * @retval CX_JSON_FORMAT_ERROR_NUMBER the JSON text contains an illegally formatted number + * @retval CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN JSON syntax error + */ +#define cxJsonFromString(allocator, str, value) \ + cx_json_from_string(allocator, cx_strcast(str), value) + /** * Creates a new (empty) JSON object. * diff -r 8972247f54e8 -r afdaa70034f8 src/json.c --- a/src/json.c Sun Dec 07 15:34:46 2025 +0100 +++ b/src/json.c Sun Dec 07 19:36:51 2025 +0100 @@ -790,6 +790,22 @@ return result; } +CxJsonStatus cx_json_from_string(const CxAllocator *allocator, + cxstring str, CxJsonValue **value) { + *value = NULL; + CxJson parser; + cxJsonInit(&parser, allocator); + if (cxJsonFill(&parser, str)) { + // LCOV_EXCL_START + cxJsonDestroy(&parser); + return CX_JSON_BUFFER_ALLOC_FAILED; + // LCOV_EXCL_STOP + } + CxJsonStatus status = cxJsonNext(&parser, value); + cxJsonDestroy(&parser); + return status; +} + void cxJsonValueFree(CxJsonValue *value) { if (value == NULL || value->type == CX_JSON_NOTHING) return; switch (value->type) { diff -r 8972247f54e8 -r afdaa70034f8 tests/test_json.c --- a/tests/test_json.c Sun Dec 07 15:34:46 2025 +0100 +++ b/tests/test_json.c Sun Dec 07 19:36:51 2025 +0100 @@ -143,6 +143,62 @@ cxJsonValueFree(obj); } +CX_TEST(test_json_from_string) { + cxstring text = cx_str( + "{\n" + "\t\"message\":\"success\",\n" + "\t\"position\":{\n" + "\t\t\"longitude\":-94.7099,\n" + "\t\t\"latitude\":51.5539\n" + "\t},\n" + "\t\"timestamp\":1729348561,\n" + "\t\"alive\":true\n" + "}" + ); + + CX_TEST_DO { + CxJsonValue *obj; + CX_TEST_ASSERT(cxJsonFromString(NULL, text, &obj) == CX_JSON_NO_ERROR); + + // check the contents + CX_TEST_ASSERT(cxJsonIsObject(obj)); + + CxJsonValue *message = cxJsonObjGet(obj, "message"); + CX_TEST_ASSERT(cxJsonIsString(message)); + CX_TEST_ASSERT(0 == cx_strcmp( + cxJsonAsCxString(message), + "success") + ); + + CxJsonValue *position = cxJsonObjGet(obj, "position"); + CX_TEST_ASSERT(cxJsonIsObject(position)); + CxJsonValue *longitude = cxJsonObjGet(position, "longitude"); + CX_TEST_ASSERT(cxJsonIsNumber(longitude)); + CX_TEST_ASSERT(!cxJsonIsInteger(longitude)); + CX_TEST_ASSERT(0 == cx_vcmp_double(cxJsonAsDouble(longitude), -94.7099)); + CX_TEST_ASSERT(cxJsonAsInteger(longitude) == -94); + CxJsonValue *latitude = cxJsonObjGet(position, "latitude"); + CX_TEST_ASSERT(cxJsonIsNumber(latitude)); + CX_TEST_ASSERT(!cxJsonIsInteger(latitude)); + CX_TEST_ASSERT(0 == cx_vcmp_double(cxJsonAsDouble(latitude), 51.5539)); + CX_TEST_ASSERT(cxJsonAsInteger(latitude) == 51); + + CxJsonValue *timestamp = cxJsonObjGet(obj, "timestamp"); + CX_TEST_ASSERT(cxJsonIsInteger(timestamp)); + CX_TEST_ASSERT(cxJsonIsNumber(timestamp)); + CX_TEST_ASSERT(cxJsonAsInteger(timestamp) == 1729348561); + CX_TEST_ASSERT(cxJsonAsDouble(timestamp) == 1729348561.0); + + CxJsonValue *alive = cxJsonObjGet(obj, "alive"); + CX_TEST_ASSERT(cxJsonIsBool(alive)); + CX_TEST_ASSERT(cxJsonIsTrue(alive)); + CX_TEST_ASSERT(!cxJsonIsFalse(alive)); + CX_TEST_ASSERT(cxJsonAsBool(alive)); + + cxJsonValueFree(obj); + } +} + CX_TEST(test_json_escaped_strings) { cxstring text = cx_str( "{\n" @@ -1462,6 +1518,7 @@ cx_test_register(suite, test_json_init_default); cx_test_register(suite, test_json_simple_object); cx_test_register(suite, test_json_large_object); + cx_test_register(suite, test_json_from_string); cx_test_register(suite, test_json_escaped_strings); cx_test_register(suite, test_json_escaped_unicode_strings); cx_test_register(suite, test_json_escaped_unicode_malformed);