JSON → TypeScript — C source
Paste any JSON and instantly get clean, typed TypeScript interfaces - primitives, nested objects, arrays and unions, all inferred. Optional keys, reserved-word quoting, and shape dedup are handled for you. Runs 100% in your browser.
This is the C implementation — the same logic the interactive tool runs, in a shareable, citable form.
/* json-to-typescript — infer TypeScript interfaces from any JSON value. C99
* port (canonical TS: src/lib/json-to-typescript/; Go twin: cli/json-to-typescript).
* Condensed: a minimal JSON scanner (assumes valid input) feeds the core
* inference — primitives map to TS primitives, arrays merge element types
* (objects merge keys; keys absent from some element become optional), and
* shapes dedupe by structural signature into named interfaces. Full port,
* with unionArrays/optionalNullable: python.py / javascript.js. */
#include <ctype.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct { char *s; size_t n, cap; } SB;
static void sb(SB *b, const char *f, ...) { va_list a; va_start(a, f); int k = vsnprintf(0, 0, f, a); va_end(a); if (b->n + k + 1 > b->cap) { b->cap = (b->n + k + 1) * 2; b->s = realloc(b->s, b->cap); } va_start(a, f); vsnprintf(b->s + b->n, k + 1, f, a); va_end(a); b->n += k; }
static char *dupb(const char *s) { static char p[1 << 14]; static int pn; char *r = p + pn; pn += strlen(s) + 1; return strcpy(r, s); } /* bump arena */
typedef struct J { int kind; struct J **kids; char **keys; int n; } J; /* 0 null 1 bool 2 num 3 str 4 arr 5 obj */
static const char *S; static int I;
static void jws(void) { while (S[I] == ' ' || S[I] == '\t' || S[I] == '\n' || S[I] == '\r') I++; }
static char *jstr(void) { SB b = {0}; I++; for (int esc = 0;;) { char c = S[I++]; if (!esc && c == '\\') { esc = 1; continue; } if (esc && c == 'u') { int cp = 0; for (int k = 0; k < 4; k++) { char h = S[I++]; cp = cp * 16 + (h <= '9' ? h - '0' : (h | 32) - 87); } sb(&b, "%c", cp); } else if (esc) sb(&b, "%c", c == 'n' ? 10 : c == 't' ? 9 : c); else if (c == '"') return b.s ? b.s : dupb(""); else sb(&b, "%c", c); esc = 0; } }
static struct J *jval(void);
static J *jadd(J *j) { j->kids = realloc(j->kids, (j->n + 1) * sizeof *j->kids); j->kids[j->n++] = jval(); return j; }
static J *jval(void) { jws(); J *j = calloc(1, sizeof *j); char c = S[I];
if (c == '{' || c == '[') { int obj = c == '{'; j->kind = obj ? 5 : 4; I++; jws();
if (S[I] != (obj ? '}' : ']')) for (;;) { if (obj) { j->keys = realloc(j->keys, (j->n + 1) * sizeof *j->keys); j->keys[j->n] = jstr(); jws(); I++; } jadd(j); jws(); char d = S[I++]; if (d == ',') { jws(); continue; } break; } else I++;
return j; }
if (c == '"') { jstr(); j->kind = 3; return j; } /* value text is unused: only the kind matters */
if (!strncmp(S + I, "true", 4) || !strncmp(S + I, "false", 5)) { j->kind = 1; I += S[I] == 't' ? 4 : 5; return j; }
if (!strncmp(S + I, "null", 4)) { I += 4; return j; }
while (S[I] && (isdigit((unsigned char)S[I]) || strchr("+-.eE", S[I]))) I++; j->kind = 2; return j; }
typedef struct T T; typedef struct { char *key; T *ty; int opt; } P;
struct T { int kind; char *prim, *sig, *hint; T *of, **mem; int nm; P *props; int np; }; /* 0 prim 1 unk 2 arr 3 obj 4 uni */
static char *pascal(const char *k) { SB b = {0}; int up = 1; for (const char *p = k; *p; p++) if (!isalnum((unsigned char)*p)) up = 1; else if (up) { sb(&b, "%c", toupper((unsigned char)*p)); up = 0; } else sb(&b, "%c", *p); if (!b.n) sb(&b, "Item"); if (isdigit((unsigned char)b.s[0])) { char *o = b.s; b.s = malloc(b.n + 2); b.s[0] = 'N'; memcpy(b.s + 1, o, b.n + 1); } return b.s; } /* `user_id` -> `UserId`; leading digit escapes with N */
static char *singular(const char *n) { size_t l = strlen(n); if (l > 1 && n[l - 1] == 's' && n[l - 2] != 's') { char *r = malloc(l); memcpy(r, n, l - 1); r[l - 1] = 0; return r; } SB b = {0}; sb(&b, "%sItem", n); return b.s; } /* trim one `s`, never `ss` */
static T *primT(char *ts) { T *t = calloc(1, sizeof *t); t->kind = 0; t->prim = t->sig = ts; return t; }
static T *arrT(T *of) { T *t = calloc(1, sizeof *t); t->kind = 2; t->of = of; SB b = {0}; if (of) sb(&b, "[%s]", of->sig); else sb(&b, "[]"); t->sig = dupb(b.s); return t; }
static T *objT(P *props, int np, char *hint) { T *t = calloc(1, sizeof *t); t->kind = 3; t->props = props; t->np = np; t->hint = hint; SB b = {0}; sb(&b, "{"); for (int i = 0; i < np; i++) sb(&b, "%s%s:%s", i ? ";" : "", props[i].key, props[i].ty->sig); sb(&b, "}"); t->sig = dupb(b.s); return t; }
static T *combine(T **nodes, int n);
static T *mergeObjs(T **objs, int n) { /* union of keys; a key absent from some element becomes optional */
P props[64]; int np = 0;
for (int o = 0; o < n; o++) for (int i = 0; i < objs[o]->np; i++) { int seen = 0; for (int q = 0; q < np; q++) if (!strcmp(props[q].key, objs[o]->props[i].key)) seen = 1; if (!seen) props[np++].key = objs[o]->props[i].key; }
for (int q = 0; q < np; q++) { T *ty[64]; int nt = 0;
for (int o = 0; o < n; o++) for (int i = 0; i < objs[o]->np; i++) if (!strcmp(objs[o]->props[i].key, props[q].key)) ty[nt++] = objs[o]->props[i].ty;
props[q].ty = combine(ty, nt); props[q].opt = nt < n; }
return objT(props, np, objs[0]->hint); }
static T *combine(T **nodes, int n) { /* condensed: objects merge; the rest dedupe to single-or-union */
if (!n) { T *t = calloc(1, sizeof *t); t->kind = 1; t->sig = dupb("?"); return t; }
int objs = 1; for (int i = 0; i < n; i++) if (nodes[i]->kind != 3) objs = 0;
if (objs) return mergeObjs(nodes, n);
T *d[64]; int nd = 0; for (int i = 0; i < n; i++) { int dup = 0; for (int q = 0; q < nd; q++) if (!strcmp(d[q]->sig, nodes[i]->sig)) dup = 1; if (!dup) d[nd++] = nodes[i]; }
if (nd == 1) return d[0];
T *t = calloc(1, sizeof *t); t->kind = 4; t->mem = malloc(nd * sizeof *t->mem); memcpy(t->mem, d, nd * sizeof *d); t->nm = nd; SB b = {0}; sb(&b, "("); for (int i = 0; i < nd; i++) sb(&b, "%s%s", i ? "|" : "", d[i]->sig); sb(&b, ")"); t->sig = dupb(b.s); return t; }
static T *infer(J *j, char *hint) {
if (j->kind == 5) { P *props = calloc(j->n ? j->n : 1, sizeof *props);
for (int i = 0; i < j->n; i++) { SB h = {0}; sb(&h, "%s%s", hint, pascal(j->keys[i])); props[i] = (P){ j->keys[i], infer(j->kids[i], h.s), 0 }; }
return objT(props, j->n, hint); }
if (j->kind == 4) { if (!j->n) return arrT(0); char *eh = singular(hint); T **el = calloc(j->n, sizeof *el); for (int i = 0; i < j->n; i++) el[i] = infer(j->kids[i], eh); return arrT(combine(el, j->n)); }
return primT((char *[]){ "null", "boolean", "number", "string" }[j->kind]); }
static char *g_sig[64], *g_name[64]; static int g_nn; static T *g_ord[64]; static int g_on;
static char *nameOf(char *sig) { for (int i = 0; i < g_nn; i++) if (!strcmp(g_sig[i], sig)) return g_name[i]; return 0; }
static int nameUsed(const char *c) { for (int i = 0; i < g_nn; i++) if (!strcmp(g_name[i], c)) return 1; return 0; }
static void collect(T *t, char *forced) { /* first-seen naming; root takes rootName; collisions get 2, 3, ... */
if (t->kind == 3) { if (!nameOf(t->sig)) { char *cand = forced ? forced : t->hint;
if (nameUsed(cand)) { char buf[128]; int i = 2; do snprintf(buf, sizeof buf, "%s%d", cand, i++); while (nameUsed(buf)); cand = dupb(buf); }
g_sig[g_nn] = t->sig; g_name[g_nn++] = cand; g_ord[g_on++] = t; }
for (int i = 0; i < t->np; i++) collect(t->props[i].ty, 0); }
else if (t->kind == 2 && t->of) collect(t->of, 0);
else if (t->kind == 4) for (int i = 0; i < t->nm; i++) collect(t->mem[i], 0); }
static const char *RES[] = { "class", "const", "default", "function", "interface", "let", "new", "null", "return", "this", "type", "var", "boolean", "number", "string", "unknown", 0 };
static void renderKey(SB *b, const char *k) { /* bareword when legal, else a quoted JSON string key */
int ok = isalpha((unsigned char)k[0]) || k[0] == '_' || k[0] == '$';
for (const char *p = k + 1; ok && *p; p++) if (!isalnum((unsigned char)*p) && *p != '_' && *p != '$') ok = 0;
for (int i = 0; ok && RES[i]; i++) if (!strcmp(RES[i], k)) ok = 0;
if (ok) sb(b, "%s", k); else sb(b, "\"%s\"", k); }
static void renderT(SB *b, T *t) {
if (t->kind == 0) sb(b, "%s", t->prim);
else if (t->kind == 1) sb(b, "unknown");
else if (t->kind == 3) sb(b, "%s", nameOf(t->sig) ? nameOf(t->sig) : "unknown");
else if (t->kind == 4) for (int i = 0; i < t->nm; i++) { if (i) sb(b, " | "); renderT(b, t->mem[i]); }
else if (!t->of) sb(b, "unknown[]");
else if (t->of->kind == 4) { sb(b, "("); renderT(b, t->of); sb(b, ")[]"); } /* parens around a union element */
else { renderT(b, t->of); sb(b, "[]"); } }
static char *json_to_ts(const char *json, const char *root_name) {
S = json; I = 0; T *root = infer(jval(), pascal(root_name)); collect(root, pascal(root_name)); SB b = {0};
for (int i = 0; i < g_on; i++) { T *o = g_ord[i]; if (i) sb(&b, "\n\n");
if (!o->np) { sb(&b, "interface %s {}", nameOf(o->sig)); continue; }
sb(&b, "interface %s {\n", nameOf(o->sig));
for (int q = 0; q < o->np; q++) { sb(&b, " "); renderKey(&b, o->props[q].key); if (o->props[q].opt) sb(&b, "?"); sb(&b, ": "); renderT(&b, o->props[q].ty); sb(&b, ";\n"); }
sb(&b, "}"); }
if (root->kind != 3) { if (g_on) sb(&b, "\n\n"); sb(&b, "type %s = ", pascal(root_name)); renderT(&b, root); sb(&b, ";"); } /* non-object roots emit a type alias */
return b.s; }
int main(void) { /* shapes dedupe by signature; array objects merge keys (name?/active? optional) */
puts(json_to_ts("{\"users\":[{\"id\":1,\"name\":\"ada\"},{\"id\":2,\"active\":true}]}", "Root"));
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 →