Skip to content

File syn_json_write.c

File List > src > syntropic > util > syn_json_write.c

Go to the documentation of this file

#if __has_include("syn_config.h")
#include "syn_config.h"
#endif

#if !defined(SYN_USE_JSON) || SYN_USE_JSON

#include "../util/syn_assert.h"
#include "syn_json_write.h"

#include <string.h>

/* ── Internal helpers ──────────────────────────────────────────────────── */

static void jw_putc(SYN_JsonWriter *w, char ch)
{
    if (w == NULL || w->overflow)
        return;
    if (w->len + 1 >= w->capacity) {
        w->overflow = true;
        return;
    }
    w->buf[w->len++] = ch;
    w->buf[w->len] = '\0';
}

static void jw_puts(SYN_JsonWriter *w, const char *s)
{
    if (w == NULL || s == NULL || w->overflow)
        return;

    size_t slen = strlen(s);
    if (w->len + slen >= w->capacity) {
        w->overflow = true;
        return;
    }
    memcpy(w->buf + w->len, s, slen);
    w->len += slen;
    w->buf[w->len] = '\0';
}

static void jw_str(SYN_JsonWriter *w, const char *s)
{
    if (w == NULL || s == NULL)
        return;
    jw_putc(w, '"');
    while (*s && !w->overflow) {
        char ch = *s++;
        switch (ch) {
        case '"':
            jw_puts(w, "\\\"");
            break;
        case '\\':
            jw_puts(w, "\\\\");
            break;
        case '\n':
            jw_puts(w, "\\n");
            break;
        case '\r':
            jw_puts(w, "\\r");
            break;
        case '\t':
            jw_puts(w, "\\t");
            break;
        default:
            if ((uint8_t)ch < 0x20) {
                /* Skip control chars */
            } else {
                jw_putc(w, ch);
            }
            break;
        }
    }
    jw_putc(w, '"');
}

static void jw_int(SYN_JsonWriter *w, int32_t val)
{
    char tmp[12]; /* -2147483648 + null = 12 */
    int pos = 0;
    uint32_t uval;
    bool neg = false;

    if (val < 0) {
        neg = true;
        uval = (uint32_t)(-(val + 1)) + 1u;
    } else {
        uval = (uint32_t)val;
    }

    /* Build digits in reverse */
    char digits[10];
    int dpos = 0;
    if (uval == 0) {
        digits[dpos++] = '0';
    } else {
        while (uval > 0) {
            digits[dpos++] = (char)('0' + (uval % 10));
            uval /= 10;
        }
    }

    if (neg)
        tmp[pos++] = '-';
    for (int i = dpos - 1; i >= 0; i--) {
        tmp[pos++] = digits[i];
    }
    tmp[pos] = '\0';

    jw_puts(w, tmp);
}

static void jw_uint(SYN_JsonWriter *w, uint32_t val)
{
    char tmp[11]; /* 4294967295 + null = 11 */
    int pos = 0;

    char digits[10];
    int dpos = 0;
    if (val == 0) {
        digits[dpos++] = '0';
    } else {
        while (val > 0) {
            digits[dpos++] = (char)('0' + (val % 10));
            val /= 10;
        }
    }

    for (int i = dpos - 1; i >= 0; i--) {
        tmp[pos++] = digits[i];
    }
    tmp[pos] = '\0';

    jw_puts(w, tmp);
}

static void jw_comma(SYN_JsonWriter *w)
{
    if (w == NULL)
        return;
    if (w->needs_comma) {
        jw_putc(w, ',');
    }
    w->needs_comma = true;
}

/* ── API ───────────────────────────────────────────────────────────────── */

void syn_json_init(SYN_JsonWriter *w, char *buf, size_t capacity)
{
    SYN_ASSERT(w != NULL);
    SYN_ASSERT(buf != NULL);
    SYN_ASSERT(capacity > 0);

    if (w == NULL || buf == NULL || capacity == 0) {
        return;
    }

    w->buf = buf;
    w->capacity = capacity;
    w->len = 0;
    w->depth = 0;
    w->needs_comma = false;
    w->overflow = false;
    w->buf[0] = '\0';
}

void syn_json_obj_open(SYN_JsonWriter *w)
{
    SYN_ASSERT(w != NULL);
    if (w == NULL) {
        return;
    }
    if (w->depth >= SYN_JSON_MAX_DEPTH) {
        w->overflow = true;
        return;
    }
    /* Only insert comma if this isn't following a key */
    if (w->needs_comma)
        jw_putc(w, ',');
    jw_putc(w, '{');
    w->depth++;
    w->needs_comma = false;
}

void syn_json_obj_close(SYN_JsonWriter *w)
{
    SYN_ASSERT(w != NULL);
    if (w == NULL) {
        return;
    }
    jw_putc(w, '}');
    if (w->depth > 0)
        w->depth--;
    w->needs_comma = true;
}

void syn_json_arr_open(SYN_JsonWriter *w)
{
    SYN_ASSERT(w != NULL);
    if (w == NULL) {
        return;
    }
    if (w->depth >= SYN_JSON_MAX_DEPTH) {
        w->overflow = true;
        return;
    }
    if (w->needs_comma)
        jw_putc(w, ',');
    jw_putc(w, '[');
    w->depth++;
    w->needs_comma = false;
}

void syn_json_arr_close(SYN_JsonWriter *w)
{
    SYN_ASSERT(w != NULL);
    if (w == NULL) {
        return;
    }
    jw_putc(w, ']');
    if (w->depth > 0)
        w->depth--;
    w->needs_comma = true;
}

void syn_json_key(SYN_JsonWriter *w, const char *key)
{
    SYN_ASSERT(w != NULL);
    SYN_ASSERT(key != NULL);
    if (w == NULL || key == NULL) {
        return;
    }
    jw_comma(w);
    jw_str(w, key);
    jw_putc(w, ':');
    w->needs_comma = false; /* value follows immediately */
}

void syn_json_key_str(SYN_JsonWriter *w, const char *key, const char *val)
{
    SYN_ASSERT(w != NULL);
    SYN_ASSERT(key != NULL);
    if (w == NULL || key == NULL) {
        return;
    }
    jw_comma(w);
    jw_str(w, key);
    jw_putc(w, ':');
    jw_str(w, val != NULL ? val : "");
}

void syn_json_key_int(SYN_JsonWriter *w, const char *key, int32_t val)
{
    SYN_ASSERT(w != NULL);
    SYN_ASSERT(key != NULL);
    jw_comma(w);
    jw_str(w, key);
    jw_putc(w, ':');
    jw_int(w, val);
}

void syn_json_key_uint(SYN_JsonWriter *w, const char *key, uint32_t val)
{
    SYN_ASSERT(w != NULL);
    SYN_ASSERT(key != NULL);
    jw_comma(w);
    jw_str(w, key);
    jw_putc(w, ':');
    jw_uint(w, val);
}

void syn_json_key_bool(SYN_JsonWriter *w, const char *key, bool val)
{
    SYN_ASSERT(w != NULL);
    SYN_ASSERT(key != NULL);
    jw_comma(w);
    jw_str(w, key);
    jw_putc(w, ':');
    jw_puts(w, val ? "true" : "false");
}

void syn_json_key_null(SYN_JsonWriter *w, const char *key)
{
    SYN_ASSERT(w != NULL);
    SYN_ASSERT(key != NULL);
    jw_comma(w);
    jw_str(w, key);
    jw_putc(w, ':');
    jw_puts(w, "null");
}

void syn_json_val_str(SYN_JsonWriter *w, const char *val)
{
    SYN_ASSERT(w != NULL);
    jw_comma(w);
    jw_str(w, val != NULL ? val : "");
}

void syn_json_val_int(SYN_JsonWriter *w, int32_t val)
{
    SYN_ASSERT(w != NULL);
    jw_comma(w);
    jw_int(w, val);
}

void syn_json_val_uint(SYN_JsonWriter *w, uint32_t val)
{
    SYN_ASSERT(w != NULL);
    jw_comma(w);
    jw_uint(w, val);
}

void syn_json_val_bool(SYN_JsonWriter *w, bool val)
{
    SYN_ASSERT(w != NULL);
    jw_comma(w);
    jw_puts(w, val ? "true" : "false");
}

#endif /* SYN_USE_JSON */