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)