Fri, 23 May 2025 12:44:24 +0200
make test-compile depend on both static and shared
the shared lib is not needed for the tests,
but when run with coverage, gcov will be confused
when outdated line information is available from
a previous shared build
# 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>