Mon, 31 Mar 2025 19:39:42 +0200
complete JSON documentation
relates to #451
# JSON The UCX JSON API allows [parsing](#parser) and [formatting](#writer) of JSON data. The parser API is similar to the [properties](properties.h.md) parser, but - due to the nature of JSON - is not allocation-free. ## Parser ```C #include <cx/json.h> void cxJsonInit(CxJson *json, const CxAllocator *allocator); void cxJsonReset(CxJson *json); int cxJsonFilln(CxJson *json, const char *buf, size_t len); int cxJsonFill(CxJson *json, AnyStr str); CxJsonStatus cxJsonNext(CxJson *json, CxJsonValue **value); void cxJsonDestroy(CxJson *json); ``` The first step is to initialize a `CxJson` structure with a call to `cxJsonInit()`, specifying the allocator that shall be used for allocating values of type `CxJsonValue`. Specifying `NULL` as `allocator` is allowed, in which case the `cxDefaultAllocator` will be used. The actual parsing is an interleaving invocation of the `cxJsonFill()` (or `cxJsonFilln()`) and `cxJsonNext()` functions. The `cxJsonFill()` function is a convenience function, that accepts UCX strings and normal zero-terminated C strings. Calling `cxJsonNext()` will return with `CX_JSON_NO_ERROR` (= zero) for each JSON value that is successfully parsed, and stores the pointer to the allocated value in the variable pointed to by `value`. > The parser is capable of parsing multiple consecutive JSON values. > If those values are not objects or arrays, they must, however, be separated by any whitespace character. When all the data from the input buffer was successfully consumed, `cxJsonNext()` returns `CX_JSON_NO_DATA`. If `cxJsonNext()` returns `CX_JSON_INCOMPLETE_DATA` it means that the input buffer is exhausted, but the parsed input does not constitute a complete JSON value. In that case, you can call `cxJsonFill()` again to add more data and continue with `cxJsonNext()`. A complete list of all status codes can be seen [below](#list-of-status-codes). 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. ### List of Status Codes Below is a full list of status codes for `cxJsonNext()`. | Status Code | Meaning | |---------------------------------------|---------------------------------------------------------------------------------------------------| | CX_JSON_NO_ERROR | A value was successfully parsed. | | | CX_JSON_NO_DATA | The input buffer does not contain more data. | | CX_JSON_INCOMPLETE_DATA | The input ends unexpectedly. Use `cxJsonFill()` to add more data before retrying. | | CX_JSON_NULL_DATA | The input buffer was never initialized. Probably you forgot to call `cxJsonFill()` at least once. | | CX_JSON_BUFFER_ALLOC_FAILED | More internal buffer was needed, but could not be allocated. | | CX_JSON_VALUE_ALLOC_FAILED | Allocating memory for a json value failed. | | CX_JSON_FORMAT_ERROR_NUMBER | A number value is incorrectly formatted. | | CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN | The tokenizer found something unexpected, i.e. the JSON string contains a syntax error. | ## Access Values ```C #include <cx/json.h> bool cxJsonIsObject(const CxJsonValue *value); bool cxJsonIsArray(const CxJsonValue *value); bool cxJsonIsString(const CxJsonValue *value); bool cxJsonIsNumber(const CxJsonValue *value); bool cxJsonIsInteger(const CxJsonValue *value); bool cxJsonIsLiteral(const CxJsonValue *value); bool cxJsonIsBool(const CxJsonValue *value); bool cxJsonIsTrue(const CxJsonValue *value); bool cxJsonIsFalse(const CxJsonValue *value); bool cxJsonIsNull(const CxJsonValue *value); char *cxJsonAsString(const CxJsonValue *value); cxstring cxJsonAsCxString(const CxJsonValue *value); cxmutstr cxJsonAsCxMutStr(const CxJsonValue *value); double cxJsonAsDouble(const CxJsonValue *value); int64_t cxJsonAsInteger(const CxJsonValue *value); bool cxJsonAsBool(const CxJsonValue *value); size_t cxJsonArrSize(const CxJsonValue *value); CxJsonValue *cxJsonArrGet(const CxJsonValue *value, size_t index); CxJsonValue *cxJsonObjGet(const CxJsonValue *value, AnyStr name); CxIterator cxJsonArrIter(const CxJsonValue *value); CxIterator cxJsonObjIter(const CxJsonValue *value); ``` The `cxJsonIsXYZ()` family functions check the type of the specified JSON value. The JSON specification only defines numbers, therefore `cxJsonIsNumber()` returns true for both floating point and integer numbers. On the other hand, `cxJsonIsInteger()` only returns true for integral numbers. The function `cxJsonIsBool()` returns true if `cxJsonIsLiteral()` returns true, but `cxJsonIsNull()` does not. > Since a literal can be `true`, `false`, or `null`, note carefully that `!cxJsonIsTrue(v)` > is in general _not_ equivalent to `cxJsonIsFalse(v)`. > > Additionally, UCX does implement the Javascript concept of a "falsy" value, meaning that > `cxJsonIsFalse()` _only_ returns true, if the value is a literal `false`. >{style="note"} The `cxJsonAsXYZ()` family of functions return the value with its corresponding C type. The functions `cxJsonAsInteger()` and `cxJsonAsDouble()` can be used for any number value. For example, if `cxJsonAsInteger()` is used on a non-integral number, a double-to-int conversion is performed. The function `cxJsonArraySize()` returns the number of items in an array value, which can be accessed via index with `cxJsonArrGet()` or via an iterator created with `cxJsonArrIter()`. The function `cxJsonObjGet()` returns the member in a JSON object associated with the specified `name`. > Both `cxJsonArrGet()` and `cxJsonObjGet()` are safe regarding access to non-existing values. > > When `cxJsonArrGet()` is used with an out-of-bounds index, or `cxJsonObjGet()` is used with a non-existent name, > they return a JSON value, that returns `false` for any `cxJsonIsXYZ()` function. > If you don't have full control over the JSON data, you should always check the datatype of a value first, before accessing it. >{style="note"} ## Deallocate Memory ```C #include <cx/json.h> void cxJsonValueFree(CxJsonValue *value); ``` Once a JSON value is not needed anymore, the memory can be deallocated with `cxJsonValueFree()`. Nested values are also recursively deallocated. > Make sure that you are not accidentally deallocating values that are still part of an object or array. > When deallocating the enclosing object/array, this will lead to a double-free. >{style="warning"} ## Create Objects ```C #include <cx/json.h> CxJsonValue* cxJsonCreateObj(const CxAllocator* allocator); CxJsonValue* cxJsonCreateArr(const CxAllocator* allocator); CxJsonValue* cxJsonCreateNumber( const CxAllocator* allocator, double num); CxJsonValue* cxJsonCreateInteger( const CxAllocator* allocator, int64_t num); CxJsonValue* cxJsonCreateString(const CxAllocator* allocator, const char *str); CxJsonValue* cxJsonCreateCxString( const CxAllocator* allocator, cxstring str); CxJsonValue* cxJsonCreateLiteral( const CxAllocator* allocator, CxJsonLiteral lit); int cxJsonArrAddNumbers(CxJsonValue* arr, const double* num, size_t count); int cxJsonArrAddIntegers(CxJsonValue* arr, const int64_t* num, size_t count); int cxJsonArrAddStrings(CxJsonValue* arr, const char* const* str, size_t count); int cxJsonArrAddCxStrings(CxJsonValue* arr, const cxstring* str, size_t count); int cxJsonArrAddLiterals(CxJsonValue* arr, const CxJsonLiteral* lit, size_t count); int cxJsonArrAddValues(CxJsonValue* arr, CxJsonValue* const* val, size_t count); int cxJsonObjPut(CxJsonValue* obj, cxstring name, CxJsonValue* child); CxJsonValue* cxJsonObjPutObj(CxJsonValue* obj, cxstring name); CxJsonValue* cxJsonObjPutArr(CxJsonValue* obj, cxstring name); CxJsonValue* cxJsonObjPutNumber(CxJsonValue* obj, cxstring name, double num); CxJsonValue* cxJsonObjPutInteger(CxJsonValue* obj, cxstring name, int64_t num); CxJsonValue* cxJsonObjPutString(CxJsonValue* obj, cxstring name, const char* str); CxJsonValue* cxJsonObjPutCxString(CxJsonValue* obj, cxstring name, cxstring str); CxJsonValue* cxJsonObjPutLiteral(CxJsonValue* obj, cxstring name, CxJsonLiteral lit); ``` The `cxJsonCreateXY()`-family of functions can be used to create JSON values which are allocated by the specified `allocator`. If you specify `NULL` as allocator, the `cxDefaultAllocator` is used. If you want to add created values to a JSON array or JSON object, you can use `cxJsonArrAddValues()` or `cxJsonObjPut()`, respectively. However, it is usually more convenient to use one of the other functions, as they automatically create the JSON value for. ```C #include <cx/json.h> CxJsonValue* arr = cxJsonCreateArr(NULL); // this is equivalent... CxJsonValue* x = cxJsonCreateInteger(NULL, 47); CxJsonValue* y = cxJsonCreateInteger(NULL, 11); cxJsonArrAddValues(arr, (CxJsonValue*[]){x, y}, 2); // ... to this cxJsonArrAddIntegers(arr, (int64_t[]){47, 11}, 2); ``` The following example shows how to construct a complete JSON object. ```C CxJsonValue *obj = cxJsonCreateObj(NULL); cxJsonObjPutLiteral(obj, CX_STR("bool"), CX_JSON_FALSE); cxJsonObjPutInteger(obj, CX_STR("int"), 47); CxJsonValue *strings = cxJsonObjPutArr(obj, CX_STR("strings")); cxJsonArrAddStrings(strings, (const char*[]) {"hello", "world"}, 2); CxJsonValue *nested = cxJsonObjPutObj(obj, CX_STR("nested")); CxJsonValue *objects = cxJsonObjPutArr(nested, CX_STR("objects")); CxJsonValue *obj_in_arr[2] = { cxJsonCreateObj(NULL), cxJsonCreateObj(NULL), }; cxJsonObjPutInteger(obj_in_arr[0], CX_STR("name1"), 1); cxJsonObjPutInteger(obj_in_arr[0], CX_STR("name2"), 3); cxJsonObjPutInteger(obj_in_arr[1], CX_STR("name2"), 7); cxJsonObjPutInteger(obj_in_arr[1], CX_STR("name1"), 3); cxJsonArrAddValues(objects, obj_in_arr, 2); cxJsonArrAddNumbers(cxJsonObjPutArr(nested, CX_STR("floats")), (double[]){3.1415, 47.11, 8.15}, 3); cxJsonArrAddLiterals(cxJsonObjPutArr(nested, CX_STR("literals")), (CxJsonLiteral[]){CX_JSON_TRUE, CX_JSON_NULL, CX_JSON_FALSE}, 3); CxJsonValue *ints = cxJsonObjPutArr(nested, CX_STR("ints")); cxJsonArrAddIntegers(ints, (int64_t[]){4, 8, 15}, 3); CxJsonValue *nested_array = cxJsonCreateArr(NULL); cxJsonArrAddValues(ints, &nested_array, 1); cxJsonArrAddIntegers(nested_array, (int64_t[]){16, 23}, 2); cxJsonArrAddIntegers(ints, (int64_t[]){42}, 1); CxJsonWriter w = cxJsonWriterPretty(true); cxJsonWrite(stdout, obj, (cx_write_func) fwrite, &w); cxJsonValueFree(obj); ``` The above code writes the following output to `stdout`: ```JSON { "bool": false, "int": 47, "nested": { "floats": [3.1415, 47.11, 8.15], "ints": [4, 8, 15, [16, 23], 42], "literals": [true, null, false], "objects": [{ "name1": 1, "name2": 3 }, { "name1": 3, "name2": 7 }] }, "strings": ["hello", "world"] } ``` ## Writer ```C #include <cx/json.h> typedef struct cx_json_writer_s { bool pretty; bool sort_members; uint8_t frac_max_digits; bool indent_space; uint8_t indent; bool escape_slash; } CxJsonWriter; CxJsonWriter cxJsonWriterCompact(void); CxJsonWriter cxJsonWriterPretty(bool use_spaces); int cxJsonWrite(void* target, const CxJsonValue* value, cx_write_func wfunc, const CxJsonWriter* settings); ``` A JSON value can be formatted with the `cxJsonWrite()` function. 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. | Setting | Compact Default | Pretty Default | Description | |-------------------|-----------------|----------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `pretty` | `false` | `true` | If true, the JSON will be formatted with line breaks and tabs or spaces. If false, output is as compact as possible without extra characters. | | `sort_members` | `true` | `true` | If false members are written in the order in which they were added. If true, they are sorted lexicographically. | | `frac_max_digits` | 6 | 6 | The maximum number of fractional digits in a number value. | | `indent_space` | ignored | depends on `use_spaces` argument | If true, use spaces for indentation, otherwise use tabs. | | `indent` | ignored | 4 | If `indent_space` is `true`, this is the number of spaces per tab. Ignored otherwise. | | `escape_slash` | `false` | `false` | If `true`, the slash character (a.k.a forward solidus: `/`) is also escaped. This is usually only needed when you want to use JSON as part of an HTML attribute. | <seealso> <category ref="apidoc"> <a href="https://ucx.sourceforge.io/api/json_8h.html">json.h</a> </category> </seealso>