ucx/cx/json.h

changeset 440
7c4b9cba09ca
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/cx/json.h	Sun Jan 05 22:00:39 2025 +0100
@@ -0,0 +1,1357 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+/**
+ * @file json.h
+ * @brief Interface for parsing data from JSON files.
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ * @copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_JSON_H
+#define UCX_JSON_H
+
+#include "common.h"
+#include "allocator.h"
+#include "string.h"
+#include "buffer.h"
+#include "array_list.h"
+
+#include <string.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/**
+ * The type of the parsed token.
+ */
+enum cx_json_token_type {
+    /**
+     * No complete token parsed, yet.
+     */
+    CX_JSON_NO_TOKEN,
+    /**
+     * The presumed token contains syntactical errors.
+     */
+    CX_JSON_TOKEN_ERROR,
+    /**
+     * A "begin of array" '[' token.
+     */
+    CX_JSON_TOKEN_BEGIN_ARRAY,
+    /**
+     * A "begin of object" '{' token.
+     */
+    CX_JSON_TOKEN_BEGIN_OBJECT,
+    /**
+     * An "end of array" ']' token.
+     */
+    CX_JSON_TOKEN_END_ARRAY,
+    /**
+     * An "end of object" '}' token.
+     */
+    CX_JSON_TOKEN_END_OBJECT,
+    /**
+     * A colon ':' token separating names and values.
+     */
+    CX_JSON_TOKEN_NAME_SEPARATOR,
+    /**
+     * A comma ',' token separating object entries or array elements.
+     */
+    CX_JSON_TOKEN_VALUE_SEPARATOR,
+    /**
+     * A string token.
+     */
+    CX_JSON_TOKEN_STRING,
+    /**
+     * A number token that can be represented as integer.
+     */
+    CX_JSON_TOKEN_INTEGER,
+    /**
+     * A number token that cannot be represented as integer.
+     */
+    CX_JSON_TOKEN_NUMBER,
+    /**
+     * A literal token.
+     */
+    CX_JSON_TOKEN_LITERAL,
+    /**
+     * A white-space token.
+     */
+    CX_JSON_TOKEN_SPACE
+};
+
+/**
+ * The type of some JSON value.
+ */
+enum cx_json_value_type {
+    /**
+     * Reserved.
+     */
+    CX_JSON_NOTHING, // this allows us to always return non-NULL values
+    /**
+     * A JSON object.
+     */
+    CX_JSON_OBJECT,
+    /**
+     * A JSON array.
+     */
+    CX_JSON_ARRAY,
+    /**
+     * A string.
+     */
+    CX_JSON_STRING,
+    /**
+     * A number that contains an integer.
+     */
+    CX_JSON_INTEGER,
+    /**
+     * A number, not necessarily an integer.
+     */
+    CX_JSON_NUMBER,
+    /**
+     * A literal (true, false, null).
+     */
+    CX_JSON_LITERAL
+};
+
+/**
+ * JSON literal types.
+ */
+enum cx_json_literal {
+    /**
+     * The @c null literal.
+     */
+    CX_JSON_NULL,
+    /**
+     * The @c true literal.
+     */
+    CX_JSON_TRUE,
+    /**
+     * The @c false literal.
+     */
+    CX_JSON_FALSE
+};
+
+/**
+ * Type alias for the token type enum.
+ */
+typedef enum cx_json_token_type CxJsonTokenType;
+/**
+ * Type alias for the value type enum.
+ */
+typedef enum cx_json_value_type CxJsonValueType;
+
+/**
+ * Type alias for the JSON parser interface.
+ */
+typedef struct cx_json_s CxJson;
+
+/**
+ * Type alias for the token struct.
+ */
+typedef struct cx_json_token_s CxJsonToken;
+
+/**
+ * Type alias for the JSON value struct.
+ */
+typedef struct cx_json_value_s CxJsonValue;
+
+/**
+ * Type alias for the JSON array struct.
+ */
+typedef struct cx_json_array_s CxJsonArray;
+/**
+ * Type alias for the JSON object struct.
+ */
+typedef struct cx_json_object_s CxJsonObject;
+/**
+ * Type alias for a JSON string.
+ */
+typedef struct cx_mutstr_s CxJsonString;
+/**
+ * Type alias for a number that can be represented as 64-bit signed integer.
+ */
+typedef int64_t CxJsonInteger;
+/**
+ * Type alias for number that is not an integer.
+ */
+typedef double CxJsonNumber;
+/**
+ * Type alias for a JSON literal.
+ */
+typedef enum cx_json_literal CxJsonLiteral;
+
+/**
+ * Type alias for a key/value pair in a JSON object.
+ */
+typedef struct cx_json_obj_value_s CxJsonObjValue;
+
+/**
+ * JSON array structure.
+ */
+struct cx_json_array_s {
+    /**
+     * The array data.
+     */
+    CX_ARRAY_DECLARE(CxJsonValue*, array);
+};
+
+/**
+ * JSON object structure.
+ */
+struct cx_json_object_s {
+    /**
+     * The key/value entries.
+     */
+    CX_ARRAY_DECLARE(CxJsonObjValue, values);
+    /**
+     * The original indices to reconstruct the order in which the members were added.
+     */
+    size_t *indices;
+};
+
+/**
+ * Structure for a key/value entry in a JSON object.
+ */
+struct cx_json_obj_value_s {
+    /**
+     * The key (or name in JSON terminology) of the value.
+     */
+    cxmutstr name;
+    /**
+     * The value.
+     */
+    CxJsonValue *value;
+};
+
+/**
+ * Structure for a JSON value.
+ */
+struct cx_json_value_s {
+    /**
+     * The allocator with which the value was allocated.
+     *
+     * Required for recursively deallocating memory of objects and arrays.
+     */
+    const CxAllocator *allocator;
+    /**
+     * The type of this value.
+     *
+     * Specifies how the @c value union shall be resolved.
+     */
+    CxJsonValueType type;
+    /**
+     * The value data.
+     */
+    union {
+        /**
+         * The array data if type is #CX_JSON_ARRAY.
+         */
+        CxJsonArray array;
+        /**
+         * The object data if type is #CX_JSON_OBJECT.
+         */
+        CxJsonObject object;
+        /**
+         * The string data if type is #CX_JSON_STRING.
+         */
+        CxJsonString string;
+        /**
+         * The integer if type is #CX_JSON_INTEGER.
+         */
+        CxJsonInteger integer;
+        /**
+         * The number if type is #CX_JSON_NUMBER.
+         */
+        CxJsonNumber number;
+        /**
+         * The literal type if type is #CX_JSON_LITERAL.
+         */
+        CxJsonLiteral literal;
+    } value;
+};
+
+/**
+ * Internally used structure for a parsed token.
+ * 
+ * You should never need to use this in your code.
+ */
+struct cx_json_token_s {
+    /**
+     * The token type.
+     */
+    CxJsonTokenType tokentype;
+    /**
+     * True, iff the @c content must be passed to cx_strfree().
+     */
+    bool allocated;
+    /**
+     * The token text, if any.
+     *
+     * This is not necessarily set when the token type already
+     * uniquely identifies the content.
+     */
+    cxmutstr content;
+};
+
+/**
+ * The JSON parser interface.
+ */
+struct cx_json_s {
+    /**
+     * The allocator used for produced JSON values.
+     */
+    const CxAllocator *allocator;
+    /**
+     * The input buffer.
+     */
+    CxBuffer buffer;
+
+    /**
+     * Used internally.
+     *
+     * Remembers the prefix of the last uncompleted token.
+     */
+    CxJsonToken uncompleted;
+
+    /**
+     * A pointer to an intermediate state of the currently parsed value.
+     *
+     * Never access this value manually.
+     */
+    CxJsonValue *parsed;
+
+    /**
+     * A pointer to an intermediate state of a currently parsed object member.
+     *
+     * Never access this value manually.
+     */
+    CxJsonObjValue uncompleted_member;
+
+    /**
+     * State stack.
+     */
+    CX_ARRAY_DECLARE_SIZED(int, states, unsigned);
+
+    /**
+     * Value buffer stack.
+     */
+    CX_ARRAY_DECLARE_SIZED(CxJsonValue*, vbuf, unsigned);
+
+    /**
+     * Internally reserved memory for the state stack.
+     */
+    int states_internal[8];
+
+    /**
+     * Internally reserved memory for the value buffer stack.
+     */
+    CxJsonValue* vbuf_internal[8];
+
+    /**
+     * Used internally.
+     */
+    bool tokenizer_escape; // TODO: check if it can be replaced with look-behind
+};
+
+/**
+ * Status codes for the json interface.
+ */
+enum cx_json_status {
+    /**
+     * Everything is fine.
+     */
+    CX_JSON_NO_ERROR,
+    /**
+     * The input buffer does not contain more data.
+     */
+    CX_JSON_NO_DATA,
+    /**
+     * The input ends unexpectedly.
+     *
+     * Refill the buffer with cxJsonFill() to complete the json data.
+     */
+    CX_JSON_INCOMPLETE_DATA,
+    /**
+     * Not used as a status and never returned by any function.
+     *
+     * You can use this enumerator to check for all "good" status results
+     * by checking if the status is less than @c CX_JSON_OK.
+     *
+     * A "good" status means, that you can refill data and continue parsing.
+     */
+    CX_JSON_OK,
+    /**
+     * The input buffer has never been filled.
+     */
+    CX_JSON_NULL_DATA,
+    /**
+     * Allocating memory for the internal buffer failed.
+     */
+    CX_JSON_BUFFER_ALLOC_FAILED,
+    /**
+     * Allocating memory for a json value failed.
+     */
+    CX_JSON_VALUE_ALLOC_FAILED,
+    /**
+     * A number value is incorrectly formatted.
+     */
+    CX_JSON_FORMAT_ERROR_NUMBER,
+    /**
+     * The tokenizer found something unexpected.
+     */
+    CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN
+};
+
+/**
+ * Typedef for the json status enum.
+ */
+typedef enum cx_json_status CxJsonStatus;
+
+/**
+ * The JSON writer settings.
+ */
+struct cx_json_writer_s {
+    /**
+     * Set true to enable pretty output.
+     */
+    bool pretty;
+    /**
+     * Set false to output the members in the order in which they were added.
+     */
+    bool sort_members;
+    /**
+     * The maximum number of fractional digits in a number value.
+     */
+    uint8_t frac_max_digits;
+    /**
+     * Set true to use spaces instead of tab characters.
+     * Indentation is only used in pretty output.
+     */
+    bool indent_space;
+    /**
+     * If @c indent_space is true, this is the number of spaces per tab.
+     * Indentation is only used in pretty output.
+     */
+    uint8_t indent;
+};
+
+/**
+ * Typedef for the json writer.
+ */
+typedef struct cx_json_writer_s CxJsonWriter;
+
+/**
+ * Creates a default writer configuration for compact output.
+ *
+ * @return new JSON writer settings
+ */
+cx_attr_nodiscard
+CxJsonWriter cxJsonWriterCompact(void);
+
+/**
+ * Creates a default writer configuration for pretty output.
+ *
+ * @param use_spaces false if you want tabs, true if you want four spaces instead
+ * @return new JSON writer settings
+ */
+cx_attr_nodiscard
+CxJsonWriter cxJsonWriterPretty(bool use_spaces);
+
+/**
+ * Writes a JSON value to a buffer or stream.
+ *
+ * This function blocks until all data is written or an error when trying
+ * to write data occurs.
+ * The write operation is not atomic in the sense that it might happen
+ * that the data is only partially written when an error occurs with no
+ * way to indicate how much data was written.
+ * To avoid this problem, you can use a CxBuffer as @p target which is
+ * unlikely to fail a write operation and either use the buffer's flush
+ * feature to relay the data or use the data in the buffer manually to
+ * write it to the actual target.
+ *
+ * @param target the buffer or stream where to write to
+ * @param value the value that shall be written
+ * @param wfunc the write function to use
+ * @param settings formatting settings (or @c NULL to use a compact default)
+ * @retval zero success
+ * @retval non-zero when no or not all data could be written
+ */
+cx_attr_nonnull_arg(1, 2, 3)
+int cxJsonWrite(
+    void* target,
+    const CxJsonValue* value,
+    cx_write_func wfunc,
+    const CxJsonWriter* settings
+);
+
+/**
+ * Initializes the json interface.
+ *
+ * @param json the json interface
+ * @param allocator the allocator that shall be used for the produced values
+ * @see cxJsonDestroy()
+ */
+cx_attr_nonnull_arg(1)
+void cxJsonInit(CxJson *json, const CxAllocator *allocator);
+
+/**
+ * Destroys the json interface.
+ *
+ * @param json the json interface
+ * @see cxJsonInit()
+ */
+cx_attr_nonnull
+void cxJsonDestroy(CxJson *json);
+
+/**
+ * Destroys and re-initializes the json interface.
+ *
+ * You might want to use this, to reset the parser after
+ * encountering a syntax error.
+ *
+ * @param json the json interface
+ */
+cx_attr_nonnull
+static inline void cxJsonReset(CxJson *json) {
+    const CxAllocator *allocator = json->allocator;
+    cxJsonDestroy(json);
+    cxJsonInit(json, allocator);
+}
+
+/**
+ * Fills the input buffer.
+ *
+ * @remark The JSON interface tries to avoid copying the input data.
+ * When you use this function and cxJsonNext() interleaving,
+ * no copies are performed. However, you must not free the
+ * pointer to the data in that case. When you invoke the fill
+ * function more than once before calling cxJsonNext(),
+ * the additional data is appended - inevitably leading to
+ * an allocation of a new buffer and copying the previous contents.
+ *
+ * @param json the json interface
+ * @param buf the source buffer
+ * @param len the length of the source buffer
+ * @retval zero success
+ * @retval non-zero internal allocation error
+ * @see cxJsonFill()
+ */
+cx_attr_nonnull
+cx_attr_access_r(2, 3)
+int cxJsonFilln(CxJson *json, const char *buf, size_t len);
+
+#ifdef __cplusplus
+} // extern "C"
+
+cx_attr_nonnull
+static inline int cxJsonFill(
+        CxJson *json,
+        cxstring str
+) {
+    return cxJsonFilln(json, str.ptr, str.length);
+}
+
+cx_attr_nonnull
+static inline int cxJsonFill(
+        CxJson *json,
+        cxmutstr str
+) {
+    return cxJsonFilln(json, str.ptr, str.length);
+}
+
+cx_attr_nonnull
+cx_attr_cstr_arg(2)
+static inline int cxJsonFill(
+        CxJson *json,
+        const char *str
+) {
+    return cxJsonFilln(json, str, strlen(str));
+}
+
+extern "C" {
+#else // __cplusplus
+/**
+ * Fills the input buffer.
+ *
+ * The JSON interface tries to avoid copying the input data.
+ * When you use this function and cxJsonNext() interleaving,
+ * no copies are performed. However, you must not free the
+ * pointer to the data in that case. When you invoke the fill
+ * function more than once before calling cxJsonNext(),
+ * the additional data is appended - inevitably leading to
+ * an allocation of a new buffer and copying the previous contents.
+ *
+ * @param json the json interface
+ * @param str the source string
+ * @retval zero success
+ * @retval non-zero internal allocation error
+ * @see cxJsonFilln()
+ */
+#define cxJsonFill(json, str) _Generic((str), \
+    cxstring: cx_json_fill_cxstr,             \
+    cxmutstr: cx_json_fill_mutstr,            \
+    char*: cx_json_fill_str,                  \
+    const char*: cx_json_fill_str)            \
+    (json, str)
+
+/**
+ * @copydoc cxJsonFill()
+ */
+cx_attr_nonnull
+static inline int cx_json_fill_cxstr(
+        CxJson *json,
+        cxstring str
+) {
+    return cxJsonFilln(json, str.ptr, str.length);
+}
+
+/**
+ * @copydoc cxJsonFill()
+ */
+cx_attr_nonnull
+static inline int cx_json_fill_mutstr(
+        CxJson *json,
+        cxmutstr str
+) {
+    return cxJsonFilln(json, str.ptr, str.length);
+}
+
+/**
+ * @copydoc cxJsonFill()
+ */
+cx_attr_nonnull
+cx_attr_cstr_arg(2)
+static inline int cx_json_fill_str(
+        CxJson *json,
+        const char *str
+) {
+    return cxJsonFilln(json, str, strlen(str));
+}
+#endif
+
+/**
+ * Creates a new (empty) JSON object.
+ *
+ * @param allocator the allocator to use
+ * @return the new JSON object or @c NULL if allocation fails
+ * @see cxJsonObjPutObj()
+ * @see cxJsonArrAddValues()
+ */
+cx_attr_nodiscard
+CxJsonValue* cxJsonCreateObj(const CxAllocator* allocator);
+
+/**
+ * Creates a new (empty) JSON array.
+ *
+ * @param allocator the allocator to use
+ * @return the new JSON array or @c NULL if allocation fails
+ * @see cxJsonObjPutArr()
+ * @see cxJsonArrAddValues()
+ */
+cx_attr_nodiscard
+CxJsonValue* cxJsonCreateArr(const CxAllocator* allocator);
+
+/**
+ * Creates a new JSON number value.
+ *
+ * @param allocator the allocator to use
+ * @param num the numeric value
+ * @return the new JSON value or @c NULL if allocation fails
+ * @see cxJsonObjPutNumber()
+ * @see cxJsonArrAddNumbers()
+ */
+cx_attr_nodiscard
+CxJsonValue* cxJsonCreateNumber(const CxAllocator* allocator, double num);
+
+/**
+ * Creates a new JSON number value based on an integer.
+ *
+ * @param allocator the allocator to use
+ * @param num the numeric value
+ * @return the new JSON value or @c NULL if allocation fails
+ * @see cxJsonObjPutInteger()
+ * @see cxJsonArrAddIntegers()
+ */
+cx_attr_nodiscard
+CxJsonValue* cxJsonCreateInteger(const CxAllocator* allocator, int64_t num);
+
+/**
+ * Creates a new JSON string.
+ *
+ * @param allocator the allocator to use
+ * @param str the string data
+ * @return the new JSON value or @c NULL if allocation fails
+ * @see cxJsonCreateString()
+ * @see cxJsonObjPutString()
+ * @see cxJsonArrAddStrings()
+ */
+cx_attr_nodiscard
+cx_attr_nonnull_arg(2)
+cx_attr_cstr_arg(2)
+CxJsonValue* cxJsonCreateString(const CxAllocator* allocator, const char *str);
+
+/**
+ * Creates a new JSON string.
+ *
+ * @param allocator the allocator to use
+ * @param str the string data
+ * @return the new JSON value or @c NULL if allocation fails
+ * @see cxJsonCreateCxString()
+ * @see cxJsonObjPutCxString()
+ * @see cxJsonArrAddCxStrings()
+ */
+cx_attr_nodiscard
+CxJsonValue* cxJsonCreateCxString(const CxAllocator* allocator, cxstring str);
+
+/**
+ * Creates a new JSON literal.
+ *
+ * @param allocator the allocator to use
+ * @param lit the type of literal
+ * @return the new JSON value or @c NULL if allocation fails
+ * @see cxJsonObjPutLiteral()
+ * @see cxJsonArrAddLiterals()
+ */
+cx_attr_nodiscard
+CxJsonValue* cxJsonCreateLiteral(const CxAllocator* allocator, CxJsonLiteral lit);
+
+/**
+ * Adds number values to a JSON array.
+ *
+ * @param arr the JSON array
+ * @param num the array of values
+ * @param count the number of elements
+ * @retval zero success
+ * @retval non-zero allocation failure
+ */
+cx_attr_nonnull
+cx_attr_access_r(2, 3)
+int cxJsonArrAddNumbers(CxJsonValue* arr, const double* num, size_t count);
+
+/**
+ * Adds number values, of which all are integers, to a JSON array.
+ *
+ * @param arr the JSON array
+ * @param num the array of values
+ * @param count the number of elements
+ * @retval zero success
+ * @retval non-zero allocation failure
+ */
+cx_attr_nonnull
+cx_attr_access_r(2, 3)
+int cxJsonArrAddIntegers(CxJsonValue* arr, const int64_t* num, size_t count);
+
+/**
+ * Adds strings to a JSON array.
+ *
+ * The strings will be copied with the allocator of the array.
+ *
+ * @param arr the JSON array
+ * @param str the array of strings
+ * @param count the number of elements
+ * @retval zero success
+ * @retval non-zero allocation failure
+ * @see cxJsonArrAddCxStrings()
+ */
+cx_attr_nonnull
+cx_attr_access_r(2, 3)
+int cxJsonArrAddStrings(CxJsonValue* arr, const char* const* str, size_t count);
+
+/**
+ * Adds strings to a JSON array.
+ *
+ * The strings will be copied with the allocator of the array.
+ *
+ * @param arr the JSON array
+ * @param str the array of strings
+ * @param count the number of elements
+ * @retval zero success
+ * @retval non-zero allocation failure
+ * @see cxJsonArrAddStrings()
+ */
+cx_attr_nonnull
+cx_attr_access_r(2, 3)
+int cxJsonArrAddCxStrings(CxJsonValue* arr, const cxstring* str, size_t count);
+
+/**
+ * Adds literals to a JSON array.
+ *
+ * @param arr the JSON array
+ * @param lit the array of literal types
+ * @param count the number of elements
+ * @retval zero success
+ * @retval non-zero allocation failure
+ */
+cx_attr_nonnull
+cx_attr_access_r(2, 3)
+int cxJsonArrAddLiterals(CxJsonValue* arr, const CxJsonLiteral* lit, size_t count);
+
+/**
+ * Add arbitrary values to a JSON array.
+ *
+ * @attention In contrast to all other add functions, this function adds the values
+ * directly to the array instead of copying them.
+ *
+ * @param arr the JSON array
+ * @param val the values
+ * @param count the number of elements
+ * @retval zero success
+ * @retval non-zero allocation failure
+ */
+cx_attr_nonnull
+cx_attr_access_r(2, 3)
+int cxJsonArrAddValues(CxJsonValue* arr, CxJsonValue* const* val, size_t count);
+
+/**
+ * Adds or replaces a value within a JSON object.
+ *
+ * The value will be directly added and not copied.
+ *
+ * @note If a value with the specified @p name already exists,
+ * it will be (recursively) freed with its own allocator.
+ *
+ * @param obj the JSON object
+ * @param name the name of the value
+ * @param child the value
+ * @retval zero success
+ * @retval non-zero allocation failure
+ */
+cx_attr_nonnull
+int cxJsonObjPut(CxJsonValue* obj, cxstring name, CxJsonValue* child);
+
+/**
+ * Creates a new JSON object and adds it to an existing object.
+ *
+ * @param obj the target JSON object
+ * @param name the name of the new value
+ * @return the new value or @c NULL if allocation fails
+ * @see cxJsonObjPut()
+ * @see cxJsonCreateObj()
+ */
+cx_attr_nonnull
+CxJsonValue* cxJsonObjPutObj(CxJsonValue* obj, cxstring name);
+
+/**
+ * Creates a new JSON array and adds it to an object.
+ *
+ * @param obj the target JSON object
+ * @param name the name of the new value
+ * @return the new value or @c NULL if allocation fails
+ * @see cxJsonObjPut()
+ * @see cxJsonCreateArr()
+ */
+cx_attr_nonnull
+CxJsonValue* cxJsonObjPutArr(CxJsonValue* obj, cxstring name);
+
+/**
+ * Creates a new JSON number and adds it to an object.
+ *
+ * @param obj the target JSON object
+ * @param name the name of the new value
+ * @param num the numeric value
+ * @return the new value or @c NULL if allocation fails
+ * @see cxJsonObjPut()
+ * @see cxJsonCreateNumber()
+ */
+cx_attr_nonnull
+CxJsonValue* cxJsonObjPutNumber(CxJsonValue* obj, cxstring name, double num);
+
+/**
+ * Creates a new JSON number, based on an integer, and adds it to an object.
+ *
+ * @param obj the target JSON object
+ * @param name the name of the new value
+ * @param num the numeric value
+ * @return the new value or @c NULL if allocation fails
+ * @see cxJsonObjPut()
+ * @see cxJsonCreateInteger()
+ */
+cx_attr_nonnull
+CxJsonValue* cxJsonObjPutInteger(CxJsonValue* obj, cxstring name, int64_t num);
+
+/**
+ * Creates a new JSON string and adds it to an object.
+ *
+ * The string data is copied.
+ *
+ * @param obj the target JSON object
+ * @param name the name of the new value
+ * @param str the string data
+ * @return the new value or @c NULL if allocation fails
+ * @see cxJsonObjPut()
+ * @see cxJsonCreateString()
+ */
+cx_attr_nonnull
+cx_attr_cstr_arg(3)
+CxJsonValue* cxJsonObjPutString(CxJsonValue* obj, cxstring name, const char* str);
+
+/**
+ * Creates a new JSON string and adds it to an object.
+ *
+ * The string data is copied.
+ *
+ * @param obj the target JSON object
+ * @param name the name of the new value
+ * @param str the string data
+ * @return the new value or @c NULL if allocation fails
+ * @see cxJsonObjPut()
+ * @see cxJsonCreateCxString()
+ */
+cx_attr_nonnull
+CxJsonValue* cxJsonObjPutCxString(CxJsonValue* obj, cxstring name, cxstring str);
+
+/**
+ * Creates a new JSON literal and adds it to an object.
+ *
+ * @param obj the target JSON object
+ * @param name the name of the new value
+ * @param lit the type of literal
+ * @return the new value or @c NULL if allocation fails
+ * @see cxJsonObjPut()
+ * @see cxJsonCreateLiteral()
+ */
+cx_attr_nonnull
+CxJsonValue* cxJsonObjPutLiteral(CxJsonValue* obj, cxstring name, CxJsonLiteral lit);
+
+/**
+ * Recursively deallocates the memory of a JSON value.
+ *
+ * @remark The type of each deallocated value will be changed
+ * to #CX_JSON_NOTHING and values of such type will be skipped
+ * by the de-allocation. That means, this function protects
+ * you from double-frees when you are accidentally freeing
+ * a nested value and then the parent value (or vice versa).
+ *
+ * @param value the value
+ */
+void cxJsonValueFree(CxJsonValue *value);
+
+/**
+ * Tries to obtain the next JSON value.
+ *
+ * Before this function can be called, the input buffer needs
+ * to be filled with cxJsonFill().
+ *
+ * When this function returns #CX_JSON_INCOMPLETE_DATA, you can
+ * add the missing data with another invocation of cxJsonFill()
+ * and then repeat the call to cxJsonNext().
+ *
+ * @param json the json interface
+ * @param value a pointer where the next value shall be stored
+ * @retval CX_JSON_NO_ERROR successfully retrieve the @p value
+ * @retval CX_JSON_NO_DATA there is no (more) data in the buffer to read from
+ * @retval CX_JSON_INCOMPLETE_DATA an incomplete value was read
+ * and more data needs to be filled
+ * @retval CX_JSON_NULL_DATA the buffer was never initialized
+ * @retval CX_JSON_BUFFER_ALLOC_FAILED allocating internal buffer space failed
+ * @retval CX_JSON_VALUE_ALLOC_FAILED allocating memory for a 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
+ */
+cx_attr_nonnull
+cx_attr_access_w(2)
+CxJsonStatus cxJsonNext(CxJson *json, CxJsonValue **value);
+
+/**
+ * Checks if the specified value is a JSON object.
+ *
+ * @param value a pointer to the value
+ * @retval true the value is a JSON object
+ * @retval false otherwise
+ */
+cx_attr_nonnull
+static inline bool cxJsonIsObject(const CxJsonValue *value) {
+    return value->type == CX_JSON_OBJECT;
+}
+
+/**
+ * Checks if the specified value is a JSON array.
+ *
+ * @param value a pointer to the value
+ * @retval true the value is a JSON array
+ * @retval false otherwise
+ */
+cx_attr_nonnull
+static inline bool cxJsonIsArray(const CxJsonValue *value) {
+    return value->type == CX_JSON_ARRAY;
+}
+
+/**
+ * Checks if the specified value is a string.
+ *
+ * @param value a pointer to the value
+ * @retval true the value is a string
+ * @retval false otherwise
+ */
+cx_attr_nonnull
+static inline bool cxJsonIsString(const CxJsonValue *value) {
+    return value->type == CX_JSON_STRING;
+}
+
+/**
+ * Checks if the specified value is a JSON number.
+ *
+ * This function will return true for both floating point and
+ * integer numbers.
+ *
+ * @param value a pointer to the value
+ * @retval true the value is a JSON number
+ * @retval false otherwise
+ * @see cxJsonIsInteger()
+ */
+cx_attr_nonnull
+static inline bool cxJsonIsNumber(const CxJsonValue *value) {
+    return value->type == CX_JSON_NUMBER || value->type == CX_JSON_INTEGER;
+}
+
+/**
+ * Checks if the specified value is an integer number.
+ *
+ * @param value a pointer to the value
+ * @retval true the value is an integer number
+ * @retval false otherwise
+ * @see cxJsonIsNumber()
+ */
+cx_attr_nonnull
+static inline bool cxJsonIsInteger(const CxJsonValue *value) {
+    return value->type == CX_JSON_INTEGER;
+}
+
+/**
+ * Checks if the specified value is a JSON literal.
+ *
+ * JSON literals are @c true, @c false, and @c null.
+ *
+ * @param value a pointer to the value
+ * @retval true the value is a JSON literal
+ * @retval false otherwise
+ * @see cxJsonIsTrue()
+ * @see cxJsonIsFalse()
+ * @see cxJsonIsNull()
+ */
+cx_attr_nonnull
+static inline bool cxJsonIsLiteral(const CxJsonValue *value) {
+    return value->type == CX_JSON_LITERAL;
+}
+
+/**
+ * Checks if the specified value is a Boolean literal.
+ *
+ * @param value a pointer to the value
+ * @retval true the value is either @c true or @c false
+ * @retval false otherwise
+ * @see cxJsonIsTrue()
+ * @see cxJsonIsFalse()
+ */
+cx_attr_nonnull
+static inline bool cxJsonIsBool(const CxJsonValue *value) {
+    return cxJsonIsLiteral(value) && value->value.literal != CX_JSON_NULL;
+}
+
+/**
+ * Checks if the specified value is @c true.
+ *
+ * @remark Be advised, that this is not the same as
+ * testing @c !cxJsonIsFalse(v).
+ *
+ * @param value a pointer to the value
+ * @retval true the value is @c true
+ * @retval false otherwise
+ * @see cxJsonIsBool()
+ * @see cxJsonIsFalse()
+ */
+cx_attr_nonnull
+static inline bool cxJsonIsTrue(const CxJsonValue *value) {
+    return cxJsonIsLiteral(value) && value->value.literal == CX_JSON_TRUE;
+}
+
+/**
+ * Checks if the specified value is @c false.
+ *
+ * @remark Be advised, that this is not the same as
+ * testing @c !cxJsonIsTrue(v).
+ *
+ * @param value a pointer to the value
+ * @retval true the value is @c false
+ * @retval false otherwise
+ * @see cxJsonIsBool()
+ * @see cxJsonIsTrue()
+ */
+cx_attr_nonnull
+static inline bool cxJsonIsFalse(const CxJsonValue *value) {
+    return cxJsonIsLiteral(value) && value->value.literal == CX_JSON_FALSE;
+}
+
+/**
+ * Checks if the specified value is @c null.
+ *
+ * @param value a pointer to the value
+ * @retval true the value is @c null
+ * @retval false otherwise
+ * @see cxJsonIsLiteral()
+ */
+cx_attr_nonnull
+static inline bool cxJsonIsNull(const CxJsonValue *value) {
+    return cxJsonIsLiteral(value) && value->value.literal == CX_JSON_NULL;
+}
+
+/**
+ * Obtains a C string from the given JSON value.
+ *
+ * If the @p value is not a string, the behavior is undefined.
+ *
+ * @param value the JSON value
+ * @return the value represented as C string
+ * @see cxJsonIsString()
+ */
+cx_attr_nonnull
+cx_attr_returns_nonnull
+static inline char *cxJsonAsString(const CxJsonValue *value) {
+    return value->value.string.ptr;
+}
+
+/**
+ * Obtains a UCX string from the given JSON value.
+ *
+ * If the @p value is not a string, the behavior is undefined.
+ *
+ * @param value the JSON value
+ * @return the value represented as UCX string
+ * @see cxJsonIsString()
+ */
+cx_attr_nonnull
+static inline cxstring cxJsonAsCxString(const CxJsonValue *value) {
+    return cx_strcast(value->value.string);
+}
+
+/**
+ * Obtains a mutable UCX string from the given JSON value.
+ *
+ * If the @p value is not a string, the behavior is undefined.
+ *
+ * @param value the JSON value
+ * @return the value represented as mutable UCX string
+ * @see cxJsonIsString()
+ */
+cx_attr_nonnull
+static inline cxmutstr cxJsonAsCxMutStr(const CxJsonValue *value) {
+    return value->value.string;
+}
+
+/**
+ * Obtains a double-precision floating point value from the given JSON value.
+ *
+ * If the @p value is not a JSON number, the behavior is undefined.
+ *
+ * @param value the JSON value
+ * @return the value represented as double
+ * @see cxJsonIsNumber()
+ */
+cx_attr_nonnull
+static inline double cxJsonAsDouble(const CxJsonValue *value) {
+    if (value->type == CX_JSON_INTEGER) {
+        return (double) value->value.integer;
+    } else {
+        return value->value.number;
+    }
+}
+
+/**
+ * Obtains a 64-bit signed integer from the given JSON value.
+ *
+ * If the @p value is not a JSON number, the behavior is undefined.
+ * If it is a JSON number, but not an integer, the value will be
+ * converted to an integer, possibly losing precision.
+ *
+ * @param value the JSON value
+ * @return the value represented as double
+ * @see cxJsonIsNumber()
+ * @see cxJsonIsInteger()
+ */
+cx_attr_nonnull
+static inline int64_t cxJsonAsInteger(const CxJsonValue *value) {
+    if (value->type == CX_JSON_INTEGER) {
+        return value->value.integer;
+    } else {
+        return (int64_t) value->value.number;
+    }
+}
+
+/**
+ * Obtains a Boolean value from the given JSON value.
+ *
+ * If the @p value is not a JSON literal, the behavior is undefined.
+ * The @c null literal is interpreted as @c false.
+ *
+ * @param value the JSON value
+ * @return the value represented as double
+ * @see cxJsonIsLiteral()
+ */
+cx_attr_nonnull
+static inline bool cxJsonAsBool(const CxJsonValue *value) {
+    return value->value.literal == CX_JSON_TRUE;
+}
+
+/**
+ * Returns the size of a JSON array.
+ *
+ * If the @p value is not a JSON array, the behavior is undefined.
+ *
+ * @param value the JSON value
+ * @return the size of the array
+ * @see cxJsonIsArray()
+ */
+cx_attr_nonnull
+static inline size_t cxJsonArrSize(const CxJsonValue *value) {
+    return value->value.array.array_size;
+}
+
+/**
+ * Returns an element from a JSON array.
+ *
+ * If the @p value is not a JSON array, the behavior is undefined.
+ *
+ * This function guarantees to return a value. If the index is
+ * out of bounds, the returned value will be of type
+ * #CX_JSON_NOTHING, but never @c NULL.
+ *
+ * @param value the JSON value
+ * @param index the index in the array
+ * @return the value at the specified index
+ * @see cxJsonIsArray()
+ */
+cx_attr_nonnull
+cx_attr_returns_nonnull
+CxJsonValue *cxJsonArrGet(const CxJsonValue *value, size_t index);
+
+/**
+ * Returns an iterator over the JSON array elements.
+ *
+ * The iterator yields values of type @c CxJsonValue* .
+ *
+ * If the @p value is not a JSON array, the behavior is undefined.
+ *
+ * @param value the JSON value
+ * @return an iterator over the array elements
+ * @see cxJsonIsArray()
+ */
+cx_attr_nonnull
+cx_attr_nodiscard
+CxIterator cxJsonArrIter(const CxJsonValue *value);
+
+/**
+ * Returns an iterator over the JSON object members.
+ *
+ * The iterator yields values of type @c CxJsonObjValue* which
+ * contain the name and value of the member.
+ *
+ * If the @p value is not a JSON object, the behavior is undefined.
+ *
+ * @param value the JSON value
+ * @return an iterator over the object members
+ * @see cxJsonIsObject()
+ */
+cx_attr_nonnull
+cx_attr_nodiscard
+CxIterator cxJsonObjIter(const CxJsonValue *value);
+
+/**
+ * @copydoc cxJsonObjGet()
+ */
+cx_attr_nonnull
+cx_attr_returns_nonnull
+CxJsonValue *cx_json_obj_get_cxstr(const CxJsonValue *value, cxstring name);
+
+#ifdef __cplusplus
+} // extern "C"
+
+CxJsonValue *cxJsonObjGet(const CxJsonValue *value, cxstring name) {
+    return cx_json_obj_get_cxstr(value, name);
+}
+
+CxJsonValue *cxJsonObjGet(const CxJsonValue *value, cxmutstr name) {
+    return cx_json_obj_get_cxstr(value, cx_strcast(name));
+}
+
+CxJsonValue *cxJsonObjGet(const CxJsonValue *value, const char *name) {
+    return cx_json_obj_get_cxstr(value, cx_str(name));
+}
+
+extern "C" {
+#else
+/**
+ * Returns a value corresponding to a key in a JSON object.
+ *
+ * If the @p value is not a JSON object, the behavior is undefined.
+ *
+ * This function guarantees to return a JSON value. If the
+ * object does not contain @p name, the returned JSON value
+ * will be of type #CX_JSON_NOTHING, but never @c NULL.
+ *
+ * @param value the JSON object
+ * @param name the key to look up
+ * @return the value corresponding to the key
+ * @see cxJsonIsObject()
+ */
+#define cxJsonObjGet(value, name) _Generic((name), \
+        cxstring: cx_json_obj_get_cxstr,           \
+        cxmutstr: cx_json_obj_get_mutstr,          \
+        char*: cx_json_obj_get_str,                \
+        const char*: cx_json_obj_get_str)          \
+        (value, name)
+
+/**
+ * @copydoc cxJsonObjGet()
+ */
+cx_attr_nonnull
+cx_attr_returns_nonnull
+static inline CxJsonValue *cx_json_obj_get_mutstr(const CxJsonValue *value, cxmutstr name) {
+    return cx_json_obj_get_cxstr(value, cx_strcast(name));
+}
+
+/**
+ * @copydoc cxJsonObjGet()
+ */
+cx_attr_nonnull
+cx_attr_returns_nonnull
+cx_attr_cstr_arg(2)
+static inline CxJsonValue *cx_json_obj_get_str(const CxJsonValue *value, const char *name) {
+    return cx_json_obj_get_cxstr(value, cx_str(name));
+}
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UCX_JSON_H */
+

mercurial