IOXD_JSON(3)libioxd Programmer's ManualIOXD_JSON(3)

NAME

ioxd/json.h - JSON written as you go, and a struct described once, serialized with one call.

SYNOPSIS

#include <ioxd.h>

#define IOXD_JSON_DEPTH 63
typedef struct { ... } ioxd_json;
ioxd_json ioxd_json_reply(ioxd_ctx *ctx);
ioxd_json ioxd_json_pipe (struct ioxd_pipe *pipe);
ioxd_json ioxd_json_mem  (char *buf, size_t cap, size_t *len);
bool ioxd_json_object(ioxd_json *j);
bool ioxd_json_array (ioxd_json *j);
bool ioxd_json_end   (ioxd_json *j);
bool ioxd_json_done  (ioxd_json *j);
bool ioxd_json_key   (ioxd_json *j, const char *name);
bool ioxd_json_string(ioxd_json *j, ioxd_slice s);
bool ioxd_json_cstr  (ioxd_json *j, const char *s);
bool ioxd_json_int   (ioxd_json *j, int64_t v);
bool ioxd_json_uint  (ioxd_json *j, uint64_t v);
bool ioxd_json_double(ioxd_json *j, double v);
bool ioxd_json_float (ioxd_json *j, float v);
bool ioxd_json_bool  (ioxd_json *j, bool v);
bool ioxd_json_null  (ioxd_json *j);
bool ioxd_json_raw   (ioxd_json *j, ioxd_slice json);
IOXD_JSON_VALUE(ioxd_json *j, x)
IOXD_JSON_FIELD(ioxd_json *j, const char *name, x)
IOXD_JSON_STRUCT(name, FIELDS)
IOXD_JSON_WRITER(name, FIELDS)

DESCRIPTION

JSON, written as you go

#define IOXD_JSON_DEPTH 63
typedef struct ioxd_json {
    enum {
        IOXD_JSON_TO_REPLY,
        IOXD_JSON_TO_PIPE,
        IOXD_JSON_TO_MEM,
    } kind;

    union {
        ioxd_ctx         *ctx;
        struct ioxd_pipe *pipe;
        struct { char *p; size_t cap, *len; } mem;
    } to;

    uint64_t has_value;                         /* per level: a value is there, so a comma is due */
    uint64_t is_object;                         /* per level: it closes with '}' rather than ']'  */
    unsigned depth;
    bool     after_key;                         /* the next value follows a key: no comma        */
    bool     failed;
} ioxd_json;
ioxd_json ioxd_json_reply(ioxd_ctx *ctx);
ioxd_json ioxd_json_pipe (struct ioxd_pipe *pipe);
ioxd_json ioxd_json_mem  (char *buf, size_t cap, size_t *len);
bool ioxd_json_object(ioxd_json *j);
bool ioxd_json_array (ioxd_json *j);
bool ioxd_json_end   (ioxd_json *j);
bool ioxd_json_done  (ioxd_json *j);
bool ioxd_json_key   (ioxd_json *j, const char *name);
bool ioxd_json_string(ioxd_json *j, ioxd_slice s);
bool ioxd_json_cstr  (ioxd_json *j, const char *s);
bool ioxd_json_int   (ioxd_json *j, int64_t v);
bool ioxd_json_uint  (ioxd_json *j, uint64_t v);
bool ioxd_json_double(ioxd_json *j, double v);
bool ioxd_json_float (ioxd_json *j, float v);
bool ioxd_json_bool  (ioxd_json *j, bool v);
bool ioxd_json_null  (ioxd_json *j);
bool ioxd_json_raw   (ioxd_json *j, ioxd_slice json);

A forward-only JSON writer, the shape of .NET's Utf8JsonWriter: no tree, no allocation. The bytes go straight into the reply - or a raw pipe, or a buffer - escaped as they are written, and stream out as the slab fills. Nesting and commas are tracked, so a handler just says what it means:

ioxd_json j = ioxd_json_reply(ctx);                  // content-type: application/json
ioxd_json_object(&j);
    ioxd_json_key(&j, "id");    ioxd_json_int(&j, id);
    ioxd_json_key(&j, "name");  ioxd_json_string(&j, name);
    ioxd_json_key(&j, "tags");  ioxd_json_array(&j);
        ioxd_json_cstr(&j, "new");
    ioxd_json_end(&j);
ioxd_json_end(&j);
if (!ioxd_json_done(&j)) { ... }                     // whole: nothing failed, nothing open

Strings are emitted byte for byte, with only '"', '\' and the control characters escaped: invalid UTF-8 goes out exactly as it came in, so untrusted input has to be validated first. Every call returns false once the sink is gone (the peer left; the buffer is full), the nesting passed IOXD_JSON_DEPTH, or the call had no place in the document - a key outside an object, a value where a key was due, an end with nothing open. The rest is then dropped, so checking the last call is enough; check done() after it to catch an end that was never written.

IOXD_JSON_DEPTH
levels: one bit of each mask below apiece
ioxd_json_reply
into the reply; sets its content type
ioxd_json_pipe
into a raw pipe's slab
ioxd_json_mem
into memory; *len is what was written
ioxd_json_object
{
ioxd_json_array
[
ioxd_json_end
} or ], whichever is open
ioxd_json_done
nothing failed, all closed
ioxd_json_key
"name":
ioxd_json_string
"...", escaped
ioxd_json_cstr
NULL is null
ioxd_json_double
the shortest that reads back the same; nan and inf become null
ioxd_json_float
the same, read back as a float: 0.1f is 0.1
ioxd_json_raw
already JSON: copied as is
IOXD_JSON_VALUE(ioxd_json *j, x)

A value by its C type, and a key with one: the _Generic picks ioxd_json_int for the integer types, _uint for the unsigned ones, _float and _double for those two, _bool, _cstr for a char pointer, _string for a slice.

IOXD_JSON_FIELD(ioxd_json *j, const char *name, x)

A key and its value in one line. The answer goes through a function so that a field written for its effect - `IOXD_JSON_FIELD(j, "n", n);` - is a plain statement and not a value the compiler sees discarded, while `if (IOXD_JSON_FIELD(j, "n", n))` still reads it.

IOXD_JSON_STRUCT(name, FIELDS)
IOXD_JSON_WRITER(name, FIELDS)

A struct described once, serialized with one call. The description is a list of fields, each line its kind, its C type (or, for a nested struct, that struct's name) and its name:

#define USER_FIELDS(X)                       \
    X(VALUE,    int64_t,      id)            \
    X(VALUE,    const char *, name)          \
    X(OBJECT,   address,      address)       \
    X(OPTIONAL, address,      billing)       \
    X(ARRAY,    const char *, tags,   n_tags)    \
    X(OBJECTS,  order,        orders, n_orders)
IOXD_JSON_STRUCT(user, USER_FIELDS)

VALUE is a scalar, written by its C type; OBJECT a nested struct held by value; OPTIONAL a pointer to one, null where the pointer is NULL; ARRAY scalars and the field holding their count; OBJECTS the same for nested structs. IOXD_JSON_STRUCT defines the struct and the function - struct user, and user_to_json(ioxd_json *, const struct user *); IOXD_JSON_WRITER only the function, for a struct declared elsewhere with the same fields. A nested struct's own IOXD_JSON_STRUCT comes first. Counts are size_t; arrays are pointers to their first element. A note beside a field is written as a block comment, the way playground/hello/main.c writes them: a // one would run on through the backslash and swallow the lines after it.

EXAMPLES

Written as you go, straight into the reply:

ioxd_json j = ioxd_json_reply(ctx);
ioxd_json_object(&j);
    IOXD_JSON_FIELD(&j, "id", id);
    IOXD_JSON_FIELD(&j, "name", name);
    ioxd_json_key(&j, "tags"); ioxd_json_array(&j);
        ioxd_json_cstr(&j, "new");
    ioxd_json_end(&j);
ioxd_json_end(&j);

A struct described once, nested objects and arrays included:

#define ORDER_FIELDS(X)        \
    X(VALUE,   int,    number)  \
    X(VALUE,   double, total)
IOXD_JSON_STRUCT(order, ORDER_FIELDS)

#define USER_FIELDS(X)                         \
    X(VALUE,   int64_t,      id)               \
    X(VALUE,   const char *, name)             /* NULL comes out as null */ \
    X(ARRAY,   const char *, tags,   n_tags)   \
    X(OBJECTS, order,        orders, n_orders)
IOXD_JSON_STRUCT(user, USER_FIELDS)

struct user u = { .id = 42, .name = "Zoe", .tags = tags, .n_tags = 2, .orders = orders, .n_orders = 1 };
ioxd_json j = ioxd_json_reply(ctx);
user_to_json(&j, &u);                                 /* {"id":42,"name":"Zoe","tags":[...],"orders":[{...}]} */

SEE ALSO

ioxd_config(3), ioxd_http(3), ioxd_router(3), ioxd_slice(3), ioxd_pipe(3), ioxd_tls(3), ioxd_examples(7), ioxd(7)

libioxd 0.1.02026-09-09IOXD_JSON(3)