add cxJsonFromString() - resolves #771

Sun, 07 Dec 2025 19:36:51 +0100

author
Mike Becker <universe@uap-core.de>
date
Sun, 07 Dec 2025 19:36:51 +0100
changeset 1556
afdaa70034f8
parent 1555
8972247f54e8
child 1557
03fbf1c99e73

add cxJsonFromString() - resolves #771

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	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
--- 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
--- 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()`.
--- 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.
  *
--- 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) {
--- 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);

mercurial