JSON to SQL INSERT — C source
Convert a JSON array of objects into SQL INSERT statements. Properly escapes strings, handles nulls, booleans, numbers, nested objects, and multi-row inserts.
This is the C implementation — the same logic the interactive tool runs, in a shareable, citable form.
/* json-to-sql — pure JSON → SQL INSERT converter. C99 port (canonical TS:
* src/lib/jsonToSql.ts; Go twin: cli/json-to-sql). No stdlib JSON codec, so —
* like the Rust port — this is a hand-rolled JSON DOM parser (objects keep
* first-seen key order via pair arrays; number tokens stay raw text) feeding
* a multi-row INSERT builder. Errors return through Res — never crashed. */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct V V;
typedef struct { char *k; V *v; } Pair; /* insertion order */
struct V { int tag; char *s; Pair *ps; int np; V **vs; int nv; };
/* tags: 0 null, 1 true, 2 false, 3 number(raw), 4 string, 5 object, 6 array */
typedef struct { int ok; char *sql; int rows; const char *err; } Res;
typedef struct { char *p; size_t n, cap; } Buf;
static const char *src; static int i; /* parser cursor */
static void ws(void) { while (src[i]==' '||src[i]=='\t'||src[i]=='\n'||src[i]=='\r') i++; }
static char peek(void) { return src[i]; }
static char take(void) { return src[i++]; }
static V *vnew(int tag) { V *v = calloc(1, sizeof *v); v->tag = tag; return v; }
static char *slc(int from) { char *s = malloc(i - from + 1); memcpy(s, src + from, i - from); s[i - from] = 0; return s; }
static char *jstr(void) { /* string token -> heap C string */
char *out = malloc(strlen(src + i) + 1), *o = out;
if (take() != '"') { free(out); return NULL; }
while (1) { char c = take(), e; int cp = 0, k;
if (c == '"') { *o = 0; return out; }
if (!c) { free(out); return NULL; } /* unterminated */
if (c == '\\') { e = take();
if (e=='"'||e=='\\'||e=='/') *o++ = e;
else if (e=='b') *o++='\b'; else if (e=='f') *o++='\f'; else if (e=='n') *o++='\n';
else if (e=='r') *o++='\r'; else if (e=='t') *o++='\t';
else if (e=='u') { /* BMP scalar folded to a byte (demo scope) */
for (k = 0; k < 4; k++) { char h = take(); cp *= 16;
if (h>='0'&&h<='9') cp += h-'0'; else if (h>='a'&&h<='f') cp += h-'a'+10;
else if (h>='A'&&h<='F') cp += h-'A'+10; else { free(out); return NULL; } }
*o++ = (char)cp;
} else { free(out); return NULL; }
} else *o++ = c;
}
}
static V *value(void) {
ws(); char c = peek(); int from; V *v, *item; char *k;
if (c == '{') {
i++; v = vnew(5); ws();
if (peek() == '}') { i++; return v; }
while (1) { ws();
if (!(k = jstr())) return NULL;
ws(); if (take() != ':') return NULL;
if (!(item = value())) return NULL;
v->ps = realloc(v->ps, (v->np + 1) * sizeof(Pair));
v->ps[v->np].k = k; v->ps[v->np].v = item; v->np++;
ws(); c = take();
if (c == '}') return v;
if (c != ',') return NULL;
}
}
if (c == '[') {
i++; v = vnew(6); ws();
if (peek() == ']') { i++; return v; }
while (1) {
if (!(item = value())) return NULL;
v->vs = realloc(v->vs, (v->nv + 1) * sizeof(V *));
v->vs[v->nv++] = item;
ws(); c = take();
if (c == ']') return v;
if (c != ',') return NULL;
}
}
if (c == '"') { v = vnew(4); if (!(v->s = jstr())) return NULL; return v; }
if (!strncmp(src + i, "null", 4)) { i += 4; return vnew(0); }
if (!strncmp(src + i, "true", 4)) { i += 4; return vnew(1); }
if (!strncmp(src + i, "false", 5)) { i += 5; return vnew(2); }
from = i;
while (peek() && strchr("-+.eE0123456789", peek())) i++;
if (i == from) return NULL;
v = vnew(3); v->s = slc(from); return v; /* raw token, String(n)-safe */
}
static void bputs(Buf *b, const char *s) {
size_t l = strlen(s);
if (b->n + l + 1 > b->cap) { while (b->cap < b->n + l + 1) b->cap = b->cap ? b->cap * 2 : 256; b->p = realloc(b->p, b->cap); }
memcpy(b->p + b->n, s, l); b->n += l; b->p[b->n] = 0;
}
static void bputc(Buf *b, char c) { char t[2] = {c, 0}; bputs(b, t); }
static void esc(Buf *o, const char *s, int mysql) {
for (; *s; s++) {
if (*s == '\'') bputs(o, "''");
else if (mysql && *s == '\\') bputs(o, "\\\\");
else if (mysql && *s == '\n') bputs(o, "\\n");
else if (mysql && *s == '\r') bputs(o, "\\r");
else if (mysql && *s == '\032') bputs(o, "\\Z"); /* SUB — ends MySQL input */
else bputc(o, *s); /* (NUL cannot occur in a C string) */
}
}
static void cstr(Buf *o, const char *s) { /* quoted compact JSON string */
bputc(o, '"');
for (; *s; s++) {
if (*s == '"' || *s == '\\') { bputc(o, '\\'); bputc(o, *s); }
else if (*s == '\n') bputs(o, "\\n"); else if (*s == '\r') bputs(o, "\\r");
else if (*s == '\t') bputs(o, "\\t"); else bputc(o, *s);
}
bputc(o, '"');
}
static void compactjson(Buf *o, const V *v) {
int k;
switch (v->tag) {
case 0: bputs(o, "null"); break; case 1: bputs(o, "true"); break; case 2: bputs(o, "false"); break;
case 3: bputs(o, v->s); break; case 4: cstr(o, v->s); break;
case 5: bputc(o, '{'); for (k = 0; k < v->np; k++) { if (k) bputc(o, ','); cstr(o, v->ps[k].k); bputc(o, ':'); compactjson(o, v->ps[k].v); } bputc(o, '}'); break;
default: bputc(o, '['); for (k = 0; k < v->nv; k++) { if (k) bputc(o, ','); compactjson(o, v->vs[k]); } bputc(o, ']'); break;
}
}
static void lit(Buf *o, const V *v, int mysql) {
switch (v->tag) {
case 0: bputs(o, "NULL"); break;
case 1: bputs(o, mysql ? "1" : "TRUE"); break; case 2: bputs(o, mysql ? "0" : "FALSE"); break;
case 3: bputs(o, v->s); break;
case 4: bputc(o, '\''); esc(o, v->s, mysql); bputc(o, '\''); break;
default: { Buf j = {0}; compactjson(&j, v); bputc(o, '\''); esc(o, j.p, mysql); bputc(o, '\''); break; } /* OBJ/ARR → JSON text */
}
}
static void ident(Buf *o, const char *n, int mysql, int quote) {
char cl[256]; int j = 0; /* [^A-Za-z0-9_] -> '_' */
for (; *n; n++) cl[j++] = ((*n>='a'&&*n<='z')||(*n>='A'&&*n<='Z')||(*n>='0'&&*n<='9')||*n=='_') ? *n : '_';
cl[j] = 0;
const char *s = j ? cl : "tbl";
if (!quote) { bputs(o, s); return; }
bputc(o, mysql ? '`' : '"'); bputs(o, s); bputc(o, mysql ? '`' : '"');
}
/* Convert a JSON document into a single multi-row INSERT. A bare object is
* one row; an array is many. Heterogeneous rows unify via first-seen columns. */
static Res json_to_insert(const char *json, const char *table, int mysql, int quote) {
Res r = { 0, 0, 0, 0 }; Buf o = { 0 }; V *single, *root; V **rows; int nrows = 1, ri, ci, ncols = 0;
const char *cols[256];
src = json; i = 0;
if (!(root = value())) { r.err = "Invalid JSON"; return r; }
if (root->tag == 6) { rows = root->vs; nrows = root->nv; }
else { single = root; rows = &single; }
if (nrows == 0) { r.err = "No rows to insert."; return r; }
for (ri = 0; ri < nrows; ri++) if (rows[ri]->tag != 5) { r.err = "Rows must be objects."; return r; }
for (ri = 0; ri < nrows; ri++) /* first-seen union of every row's keys */
for (ci = 0; ci < rows[ri]->np; ci++) {
const char *k = rows[ri]->ps[ci].k; int seen = 0, t;
for (t = 0; t < ncols; t++) if (!strcmp(cols[t], k)) seen = 1;
if (!seen) cols[ncols++] = k;
}
bputs(&o, "INSERT INTO "); ident(&o, table, mysql, quote); bputs(&o, " (");
for (ci = 0; ci < ncols; ci++) { if (ci) bputs(&o, ", "); ident(&o, cols[ci], mysql, quote); }
bputs(&o, ") VALUES\n");
for (ri = 0; ri < nrows; ri++) {
int cj; bputs(&o, " (");
for (cj = 0; cj < ncols; cj++) {
V *cell = NULL; int f;
if (cj) bputs(&o, ", ");
for (f = 0; f < rows[ri]->np; f++) if (!strcmp(rows[ri]->ps[f].k, cols[cj])) cell = rows[ri]->ps[f].v;
if (cell) lit(&o, cell, mysql); else bputs(&o, "NULL"); /* absent -> NULL */
}
bputs(&o, ri == nrows - 1 ? ");" : "),\n");
}
r.ok = 1; r.sql = o.p; r.rows = nrows; return r;
}
int main(void) {
Res r = json_to_insert("[{\"id\":1,\"name\":\"O'Hara\"},{\"id\":2,\"age\":30.5,\"tags\":[\"a\",\"b\"]}]", "users", 0, 1);
puts(r.sql); /* INSERT INTO "users" ("id", "name", "age", "tags") VALUES ... */
return 0;
}
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →