src/cx/json.h

Fri, 23 May 2025 12:44:24 +0200

author
Mike Becker <universe@uap-core.de>
date
Fri, 23 May 2025 12:44:24 +0200
changeset 1327
ed75dc1db503
parent 1193
cfa44f3f5e3b
permissions
-rw-r--r--

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

/*
 * 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, if 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];
};

/**
 * 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.
     * The default value is 6 and values larger than 15 are reduced to 15.
     * Note, that the actual number of digits may be lower, depending on the concrete number.
     */
    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;
    /**
     * Set true to enable escaping of the slash character (solidus).
     */
    bool escape_slash;
};

/**
 * 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
cx_attr_export
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
cx_attr_export
CxJsonWriter cxJsonWriterPretty(bool use_spaces);

/**
 * Writes a JSON value to a buffer or stream.
 *
 * This function blocks until either all data is written, or an error 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)
cx_attr_export
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)
cx_attr_export
void cxJsonInit(CxJson *json, const CxAllocator *allocator);

/**
 * Destroys the json interface.
 *
 * @param json the json interface
 * @see cxJsonInit()
 */
cx_attr_nonnull
cx_attr_export
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)
cx_attr_export
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
cx_attr_export
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
cx_attr_export
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
cx_attr_export
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
cx_attr_export
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)
cx_attr_export
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
cx_attr_export
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
cx_attr_export
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)
cx_attr_export
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)
cx_attr_export
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)
cx_attr_export
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)
cx_attr_export
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)
cx_attr_export
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)
cx_attr_export
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
cx_attr_export
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
cx_attr_export
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
cx_attr_export
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
cx_attr_export
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
cx_attr_export
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)
cx_attr_export
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
cx_attr_export
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
cx_attr_export
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
 */
cx_attr_export
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)
cx_attr_export
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
cx_attr_export
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
cx_attr_export
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
cx_attr_export
CxIterator cxJsonObjIter(const CxJsonValue *value);

/**
 * @copydoc cxJsonObjGet()
 */
cx_attr_nonnull
cx_attr_returns_nonnull
cx_attr_export
CxJsonValue *cx_json_obj_get_cxstr(const CxJsonValue *value, cxstring name);

#ifdef __cplusplus
} // extern "C"

static inline CxJsonValue *cxJsonObjGet(const CxJsonValue *value, cxstring name) {
    return cx_json_obj_get_cxstr(value, name);
}

static inline CxJsonValue *cxJsonObjGet(const CxJsonValue *value, cxmutstr name) {
    return cx_json_obj_get_cxstr(value, cx_strcast(name));
}

static inline 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