src/json.c

changeset 1072
c89283cd559b
parent 1067
7799addf475f
child 1075
0cc4b63a0ae0
--- a/src/json.c	Wed Jan 01 15:26:50 2025 +0100
+++ b/src/json.c	Wed Jan 01 15:33:41 2025 +0100
@@ -34,6 +34,7 @@
 #include <assert.h>
 #include <stdio.h>
 #include <errno.h>
+#include <inttypes.h>
 
 /*
  * RFC 8259
@@ -889,37 +890,19 @@
     return value->value.array.array[index];
 }
 
-static void *cx_json_iter_current(const void *it) {
-    const CxIterator *iter = it;
-    return *(CxJsonValue**)iter->elem_handle;
-}
-
-static bool cx_json_iter_valid(const void *it) {
-    const CxIterator *iter = it;
-    return iter->index < iter->elem_count;
-}
-
-static void cx_json_iter_next(void *it) {
-    CxIterator *iter = it;
-    iter->index++;
-    iter->elem_handle = (char *) iter->elem_handle + sizeof(void *);
+CxIterator cxJsonArrIter(const CxJsonValue *value) {
+    return cxIteratorPtr(
+        value->value.array.array,
+        value->value.array.array_size
+    );
 }
 
-CxIterator cxJsonArrIter(const CxJsonValue *value) {
-    CxIterator iter;
-
-    iter.index = 0;
-    iter.elem_count = value->value.array.array_size;
-    iter.src_handle.m = value->value.array.array;
-    iter.elem_handle = iter.src_handle.m;
-    iter.elem_size = sizeof(CxJsonValue*);
-    iter.base.valid = cx_json_iter_valid;
-    iter.base.current = cx_json_iter_current;
-    iter.base.next = cx_json_iter_next;
-    iter.base.remove = false;
-    iter.base.mutating = false;
-
-    return iter;
+CxIterator cxJsonObjIter(const CxJsonValue *value) {
+    return cxIterator(
+        value->value.object.values,
+        sizeof(CxJsonObjValue),
+        value->value.object.values_size
+    );
 }
 
 CxJsonValue *cx_json_obj_get_cxstr(const CxJsonValue *value, cxstring name) {
@@ -930,3 +913,186 @@
         return member->value;
     }
 }
+
+static const CxJsonWriter cx_json_writer_default = {
+    false,
+    true,
+    255,
+    false,
+    0,
+    false,
+    0
+};
+
+// TODO: add default for pretty printing and add functions to create default structs
+
+
+int cx_json_write_rec(
+    void *target,
+    const CxJsonValue *value,
+    cx_write_func wfunc,
+    const CxJsonWriter *settings,
+    unsigned int depth
+) {
+    // TODO: implement indentation
+
+    // keep track of written items
+    size_t actual = 0, expected = 0;
+
+    // small buffer for number to string conversions
+    char numbuf[32];
+
+    // recursively write the values
+    switch (value->type) {
+        case CX_JSON_OBJECT: {
+            const char *begin_obj = "{\n";
+            if (settings->pretty) {
+                actual += wfunc(begin_obj, 1, 2, target);
+                expected += 2;
+            } else {
+                actual += wfunc(begin_obj, 1, 1, target);
+                expected++;
+            }
+            CxIterator iter = cxJsonObjIter(value);
+            cx_foreach(CxJsonObjValue*, member, iter) {
+                // the name
+                actual += wfunc("\"", 1, 1, target);
+                // TODO: escape the string
+                actual += wfunc(member->name.ptr, 1,
+                    member->name.length, target);
+                actual += wfunc("\"", 1, 1, target);
+                const char *obj_name_sep = ": ";
+                if (settings->pretty) {
+                    actual += wfunc(obj_name_sep, 1, 2, target);
+                    expected += 4 + member->name.length;
+                } else {
+                    actual += wfunc(obj_name_sep, 1, 1, target);
+                    expected += 3 + member->name.length;
+                }
+
+                // the value
+                if (0 == cx_json_write_rec(
+                        target, member->value,
+                        wfunc, settings, depth + 1)
+                ) {
+                    actual++; // count the nested values as one item
+                }
+                expected++;
+
+                // end of object-value
+                if (iter.index < iter.elem_count - 1) {
+                    const char *obj_value_sep = ",\n";
+                    if (settings->pretty) {
+                        actual += wfunc(obj_value_sep, 1, 2, target);
+                        expected += 2;
+                    } else {
+                        actual += wfunc(obj_value_sep, 1, 1, target);
+                        expected++;
+                    }
+                } else {
+                    if (settings->pretty) {
+                        actual += wfunc("\n", 1, 1, target);
+                        expected ++;
+                    }
+                }
+            }
+            actual += wfunc("}", 1, 1, target);
+            expected++;
+            break;
+        }
+        case CX_JSON_ARRAY: {
+            // TODO: implement array wrapping
+            actual += wfunc("[", 1, 1, target);
+            expected++;
+            CxIterator iter = cxJsonArrIter(value);
+            cx_foreach(CxJsonValue*, element, iter) {
+                // TODO: pretty printing obj elements vs. primitives
+                if (0 == cx_json_write_rec(
+                        target, element,
+                        wfunc, settings, depth + 1)
+                ) {
+                    actual++; // count the nested values as one item
+                }
+                expected++;
+
+                if (iter.index < iter.elem_count - 1) {
+                    const char *arr_value_sep = ", ";
+                    if (settings->pretty) {
+                        actual += wfunc(arr_value_sep, 1, 2, target);
+                        expected += 2;
+                    } else {
+                        actual += wfunc(arr_value_sep, 1, 1, target);
+                        expected++;
+                    }
+                }
+            }
+            actual += wfunc("]", 1, 1, target);
+            expected++;
+            break;
+        }
+        case CX_JSON_STRING: {
+            actual += wfunc("\"", 1, 1, target);
+            // TODO: escape the string
+            actual += wfunc(value->value.string.ptr, 1,
+                value->value.string.length, target);
+            actual += wfunc("\"", 1, 1, target);
+            expected += 2 + value->value.string.length;
+            break;
+        }
+        case CX_JSON_NUMBER: {
+            // TODO: locale bullshit
+            // TODO: formatting settings
+            snprintf(numbuf, 32, "%g", value->value.number);
+            size_t len = strlen(numbuf);
+            actual += wfunc(numbuf, 1, len, target);
+            expected += len;
+            break;
+        }
+        case CX_JSON_INTEGER: {
+            snprintf(numbuf, 32, "%" PRIi64, value->value.integer);
+            size_t len = strlen(numbuf);
+            actual += wfunc(numbuf, 1, len, target);
+            expected += len;
+            break;
+        }
+        case CX_JSON_LITERAL: {
+            if (value->value.literal == CX_JSON_TRUE) {
+                actual += wfunc("true", 1, 4, target);
+                expected += 4;
+            } else if (value->value.literal == CX_JSON_FALSE) {
+                actual += wfunc("false", 1, 5, target);
+                expected += 5;
+            } else {
+                actual += wfunc("null", 1, 4, target);
+                expected += 4;
+            }
+            break;
+        }
+        case CX_JSON_NOTHING: {
+            // deliberately supported as an empty string!
+            // users might want to just write the result
+            // of a get operation without testing the value
+            // and therefore this should not blow up
+            break;
+        }
+        default: assert(false); // LCOV_EXCL_LINE
+    }
+
+    return expected != actual;
+}
+
+int cxJsonWrite(
+    void *target,
+    const CxJsonValue *value,
+    cx_write_func wfunc,
+    const CxJsonWriter *settings
+) {
+    if (settings == NULL) {
+        settings = &cx_json_writer_default;
+    }
+    assert(target != NULL);
+    assert(value != NULL);
+    assert(wfunc != NULL);
+
+    return cx_json_write_rec(target, value, wfunc, settings, 0);
+}

mercurial