File syn_json_read.c¶
File List > src > syntropic > util > syn_json_read.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_read.h"
#include <string.h>
/* ── Internal helpers ──────────────────────────────────────────────────── */
static char *skip_ws(char *p, const char *end)
{
while (p < end && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')) {
p++;
}
return p;
}
static int32_t parse_int(const char *s)
{
bool neg = false;
if (*s == '-') {
neg = true;
s++;
}
int32_t val = 0;
while (*s >= '0' && *s <= '9') {
val = val * 10 + (*s - '0');
s++;
}
return neg ? -val : val;
}
static char *parse_string(char *p, const char *end, const char **out)
{
if (p >= end || *p != '"')
return NULL;
p++; /* skip opening quote */
*out = p;
while (p < end) {
if (*p == '\\') {
/* Skip escaped char (we don't unescape in-place for simplicity) */
p++;
if (p < end)
p++;
continue;
}
if (*p == '"') {
*p = '\0'; /* null-terminate */
return p + 1;
}
p++;
}
return NULL; /* unterminated string */
}
static char *skip_value(char *p, const char *end)
{
p = skip_ws(p, end);
if (p >= end)
return NULL;
if (*p == '"') {
/* String */
p++;
while (p < end) {
if (*p == '\\') {
p += 2;
continue;
}
if (*p == '"')
return p + 1;
p++;
}
return NULL;
}
if (*p == '{' || *p == '[') {
/* Object or array — count nesting */
char open = *p;
char close = (open == '{') ? '}' : ']';
int depth = 1;
p++;
while (p < end && depth > 0) {
if (*p == '"') {
p++;
while (p < end && *p != '"') {
if (*p == '\\')
p++;
p++;
}
if (p < end)
p++; /* skip closing quote */
continue;
}
if (*p == open)
depth++;
if (*p == close)
depth--;
p++;
}
return (depth == 0) ? p : NULL;
}
/* Number, bool, or null — read until delimiter */
while (p < end && *p != ',' && *p != '}' && *p != ']' && *p != ' ' && *p != '\t' &&
*p != '\n' && *p != '\r') {
p++;
}
return p;
}
static char *parse_object(SYN_JsonReader *r, char *p, const char *end, uint8_t depth)
{
p = skip_ws(p, end);
if (p >= end || *p != '{')
return NULL;
p++; /* skip '{' */
for (;;) {
p = skip_ws(p, end);
if (p >= end)
return NULL;
if (*p == '}')
return p + 1;
/* Parse key */
const char *key = NULL;
p = parse_string(p, end, &key);
if (p == NULL)
return NULL;
p = skip_ws(p, end);
if (p >= end || *p != ':')
return NULL;
p++; /* skip ':' */
p = skip_ws(p, end);
if (p >= end)
return NULL;
/* Check if we have room for another token */
if (r->token_count >= SYN_JSON_MAX_TOKENS) {
/* Skip the value */
p = skip_value(p, end);
if (p == NULL)
return NULL;
} else if (*p == '{') {
/* Nested object — add a marker token and recurse */
SYN_JsonToken *tok = &r->tokens[r->token_count++];
tok->type = SYN_JSON_OBJECT;
tok->key = key;
tok->value = NULL;
tok->int_val = 0;
tok->depth = depth;
p = parse_object(r, p, end, depth + 1);
if (p == NULL)
return NULL;
} else if (*p == '[') {
/* Array — add a marker token, skip for now */
SYN_JsonToken *tok = &r->tokens[r->token_count++];
tok->type = SYN_JSON_ARRAY;
tok->key = key;
tok->value = NULL;
tok->int_val = 0;
tok->depth = depth;
p = skip_value(p - 0, end); /* skip_value handles '[' */
if (p == NULL)
return NULL;
} else if (*p == '"') {
/* String value */
const char *val = NULL;
p = parse_string(p, end, &val);
if (p == NULL)
return NULL;
SYN_JsonToken *tok = &r->tokens[r->token_count++];
tok->type = SYN_JSON_STRING;
tok->key = key;
tok->value = val;
tok->int_val = 0;
tok->depth = depth;
} else {
/* Number, bool, or null */
const char *val_start = p;
p = skip_value(p, end);
if (p == NULL)
return NULL;
/* Null-terminate the value by inserting \0 */
char saved = *p;
*p = '\0';
SYN_JsonToken *tok = &r->tokens[r->token_count++];
tok->key = key;
tok->value = val_start;
tok->depth = depth;
if (val_start[0] == 't' || val_start[0] == 'f') {
tok->type = SYN_JSON_BOOL;
tok->int_val = (val_start[0] == 't') ? 1 : 0;
} else if ((p - val_start) == 4 && memcmp(val_start, "null", 4) == 0) {
tok->type = SYN_JSON_NULL;
tok->int_val = 0;
} else {
tok->type = SYN_JSON_NUMBER;
tok->int_val = parse_int(val_start);
}
/* Restore delimiter if needed for further parsing */
if (saved != ',' && saved != '}' && saved != ']') {
/* Was whitespace — no need to restore */
} else {
*p = saved;
}
}
/* Next element or end of object */
p = skip_ws(p, end);
if (p >= end)
return NULL;
if (*p == ',') {
p++;
continue;
}
if (*p == '}')
return p + 1;
return NULL; /* unexpected char */
}
}
/* ── API ───────────────────────────────────────────────────────────────── */
bool syn_json_parse(SYN_JsonReader *r, char *json, size_t len)
{
SYN_ASSERT(r != NULL);
SYN_ASSERT(json != NULL);
if (r == NULL || json == NULL || len == 0) {
return false;
}
memset(r, 0, sizeof(*r));
const char *end = json + len;
char *p = skip_ws(json, end);
if (p >= end || *p != '{') {
r->valid = false;
return false;
}
const char *result = parse_object(r, p, end, 0);
r->valid = (result != NULL);
return r->valid;
}
const SYN_JsonToken *syn_json_find(const SYN_JsonReader *r, const char *key)
{
SYN_ASSERT(r != NULL);
SYN_ASSERT(key != NULL);
if (r == NULL || key == NULL || !r->valid)
return NULL;
/* Check for dot notation: "parent.child" */
const char *dot = strchr(key, '.');
if (dot == NULL) {
/* Simple key lookup at depth 0 */
for (size_t i = 0; i < r->token_count; i++) {
const SYN_JsonToken *tok = &r->tokens[i];
if (tok->depth == 0 && tok->key != NULL && strcmp(tok->key, key) == 0) {
return tok;
}
}
return NULL;
}
/* Dot notation: find parent object, then child key */
size_t parent_len = (size_t)(dot - key);
const char *child_key = dot + 1;
/* Find the parent object token */
size_t parent_idx = 0;
bool found_parent = false;
for (size_t i = 0; i < r->token_count; i++) {
const SYN_JsonToken *tok = &r->tokens[i];
if (tok->depth == 0 && tok->type == SYN_JSON_OBJECT && tok->key != NULL &&
strlen(tok->key) == parent_len && memcmp(tok->key, key, parent_len) == 0) {
parent_idx = i;
found_parent = true;
break;
}
}
if (!found_parent)
return NULL;
/* Search tokens after the parent for child at depth 1 */
for (size_t i = parent_idx + 1; i < r->token_count; i++) {
const SYN_JsonToken *tok = &r->tokens[i];
if (tok->depth < 1)
break; /* left the parent object */
if (tok->depth == 1 && tok->key != NULL && strcmp(tok->key, child_key) == 0) {
return tok;
}
}
return NULL;
}
bool syn_json_get_str(const SYN_JsonReader *r, const char *key, char *out, size_t out_sz)
{
if (r == NULL || key == NULL || out == NULL || out_sz == 0) {
return false;
}
const SYN_JsonToken *tok = syn_json_find(r, key);
if (tok == NULL || tok->type != SYN_JSON_STRING)
return false;
if (tok->value == NULL)
return false;
size_t vlen = strlen(tok->value);
if (vlen >= out_sz)
vlen = out_sz - 1;
memcpy(out, tok->value, vlen);
out[vlen] = '\0';
return true;
}
bool syn_json_get_int(const SYN_JsonReader *r, const char *key, int32_t *out)
{
const SYN_JsonToken *tok = syn_json_find(r, key);
if (tok == NULL || tok->type != SYN_JSON_NUMBER)
return false;
*out = tok->int_val;
return true;
}
bool syn_json_get_bool(const SYN_JsonReader *r, const char *key, bool *out)
{
const SYN_JsonToken *tok = syn_json_find(r, key);
if (tok == NULL || tok->type != SYN_JSON_BOOL)
return false;
*out = (tok->int_val != 0);
return true;
}
bool syn_json_is_null(const SYN_JsonReader *r, const char *key)
{
const SYN_JsonToken *tok = syn_json_find(r, key);
return (tok != NULL && tok->type == SYN_JSON_NULL);
}
SYN_JsonType syn_json_get_type(const SYN_JsonReader *r, const char *key)
{
const SYN_JsonToken *tok = syn_json_find(r, key);
return (tok != NULL) ? tok->type : SYN_JSON_NONE;
}
#endif /* SYN_USE_JSON */