--- 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); +}