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. C++17
// port (canonical TS: src/lib/json-to-typescript/; Go twin: cli/json-to-typescript).
// Condensed: a minimal order-preserving JSON parser (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 <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <memory>
#include <string>
#include <utility>
#include <vector>
using std::string; using std::vector; using std::pair; using std::unique_ptr; using std::make_unique; using std::to_string;
struct JV { // parsed JSON — values need only a kind; keys keep their text
enum Kind { NUL, BOOL, NUM, STR, ARR, OBJ } kind = NUL;
vector<JV> arr; vector<pair<string, JV>> obj; // insertion order kept
};
struct Parser {
const string& s; size_t i = 0;
void ws() { while (i < s.size() && (s[i] == ' ' || s[i] == '\t' || s[i] == '\n' || s[i] == '\r')) i++; }
string str() { // scan a quoted string, decoding escapes
string out; i++;
for (bool esc = false;;) { char c = s[i++];
if (!esc && c == '\\') { esc = true; 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) - 'a' + 10); } out += cp; }
else if (esc) out += c == 'n' ? '\n' : c == 't' ? '\t' : c;
else if (c == '"') return out;
else out += c;
esc = false; } }
JV val() { ws(); JV j; char c = s[i];
if (c == '{' || c == '[') { bool obj = c == '{'; j.kind = obj ? JV::OBJ : JV::ARR; i++; ws();
if (s[i] != (obj ? '}' : ']')) for (;;) {
if (obj) { auto k = str(); ws(); i++; j.obj.emplace_back(std::move(k), val()); } else j.arr.push_back(val());
ws(); char d = s[i++]; if (d == ',') { ws(); continue; } break; } else i++;
return j; }
if (c == '"') { str(); j.kind = JV::STR; return j; } // value text unused: only the kind matters
if (!s.compare(i, 4, "true") || !s.compare(i, 5, "false")) { j.kind = JV::BOOL; i += s[i] == 't' ? 4 : 5; return j; }
if (!s.compare(i, 4, "null")) { i += 4; return j; }
while (i < s.size() && (isdigit((unsigned char)s[i]) || strchr("+-eE.", s[i]))) i++; j.kind = JV::NUM; return j; }
};
struct Ty; using TyP = unique_ptr<Ty>;
struct Prop { string key; TyP ty; bool opt = false; };
struct Ty { int kind; string prim, sig, hint; TyP of; vector<TyP> mem; vector<Prop> props; }; // 0 prim 1 unk 2 arr 3 obj 4 uni
string pascal(const string& k) { // `user_id` -> `UserId`; a leading digit escapes with N
string o; bool up = true;
for (char c : k) if (!isalnum((unsigned char)c)) up = true; else { o += up ? (char)toupper((unsigned char)c) : c; up = false; }
if (o.empty()) o = "Item";
return isdigit((unsigned char)o[0]) ? "N" + o : o; }
string singular(const string& n) { if (n.size() > 1 && n.back() == 's' && n[n.size() - 2] != 's') return n.substr(0, n.size() - 1); return n + "Item"; }
TyP prim(const char* ts) { auto t = make_unique<Ty>(); t->kind = 0; t->prim = t->sig = ts; return t; }
TyP arrT(TyP of) { auto t = make_unique<Ty>(); t->kind = 2; t->sig = of ? "[" + of->sig + "]" : "[]"; t->of = std::move(of); return t; }
TyP objT(vector<Prop> props, string hint) { auto t = make_unique<Ty>(); t->kind = 3; t->sig = "{";
for (size_t i = 0; i < props.size(); i++) t->sig += (i ? ";" : "") + props[i].key + ":" + props[i].ty->sig;
t->sig += "}"; t->hint = std::move(hint); t->props = std::move(props); return t; }
TyP combine(vector<TyP> nodes);
TyP mergeObjs(vector<TyP>& objs) { // union of keys; a key absent from some element becomes optional
vector<string> keys;
for (auto& o : objs) for (auto& p : o->props)
if (find_if(keys.begin(), keys.end(), [&](auto& k) { return k == p.key; }) == keys.end()) keys.push_back(p.key);
vector<Prop> props;
for (auto& k : keys) { vector<TyP> types; size_t present = 0;
for (auto& o : objs) for (auto& p : o->props) if (p.key == k) { types.push_back(std::move(p.ty)); present++; }
props.push_back({ k, combine(std::move(types)), present < objs.size() }); }
string hint = objs[0]->hint;
return objT(std::move(props), std::move(hint)); }
TyP combine(vector<TyP> nodes) { // condensed: objects merge; the rest dedupe to single-or-union
if (nodes.empty()) { auto t = make_unique<Ty>(); t->kind = 1; t->sig = "?"; return t; }
bool objs = true; for (auto& n : nodes) if (n->kind != 3) objs = false;
if (objs) return mergeObjs(nodes);
vector<TyP> d; // dedupe by structural signature, first-seen order
for (auto& n : nodes) if (none_of(d.begin(), d.end(), [&](auto& u) { return u->sig == n->sig; })) d.push_back(std::move(n));
if (d.size() == 1) return std::move(d[0]);
auto t = make_unique<Ty>(); t->kind = 4; t->sig = "(";
for (size_t i = 0; i < d.size(); i++) t->sig += (i ? "|" : "") + d[i]->sig;
t->sig += ")"; t->mem = std::move(d); return t; }
TyP infer(const JV& j, const string& hint) {
if (j.kind == JV::OBJ) { vector<Prop> props;
for (auto& [k, v] : j.obj) props.push_back({ k, infer(v, hint + pascal(k)), false });
return objT(std::move(props), hint); }
if (j.kind == JV::ARR) { if (j.arr.empty()) return arrT(nullptr);
string eh = singular(hint); vector<TyP> el;
for (auto& e : j.arr) el.push_back(infer(e, eh));
return arrT(combine(std::move(el))); }
return prim(j.kind == JV::NUL ? "null" : j.kind == JV::BOOL ? "boolean" : j.kind == JV::NUM ? "number" : "string"); }
vector<pair<string, string>> g_names; vector<Ty*> g_order; // naming: first-seen; root takes rootName; collisions get 2, 3, ...
string nameOf(const string& sig) { for (auto& [s, n] : g_names) if (s == sig) return n; return ""; }
bool nameUsed(const string& n) { for (auto& [s, x] : g_names) if (x == n) return true; return false; }
void collect(Ty* t, const char* forced) {
if (t->kind == 3) { if (nameOf(t->sig).empty()) { string cand = forced ? forced : t->hint;
if (nameUsed(cand)) { int i = 2; while (nameUsed(cand + to_string(i))) i++; cand += to_string(i); }
g_names.emplace_back(t->sig, cand); g_order.push_back(t); }
for (auto& p : t->props) collect(p.ty.get(), nullptr); }
else if (t->kind == 2 && t->of) collect(t->of.get(), nullptr);
else if (t->kind == 4) for (auto& m : t->mem) collect(m.get(), nullptr); }
string renderKey(const string& k) { // bareword when legal, else a quoted string key (no escapes needed here)
static const char* RES[] = { "class", "const", "default", "function", "interface", "let", "new", "null",
"return", "this", "type", "var", "boolean", "number", "string", "unknown" };
bool ok = isalpha((unsigned char)k[0]) || k[0] == '_' || k[0] == '$';
for (size_t i = 1; ok && i < k.size(); i++) if (!isalnum((unsigned char)k[i]) && k[i] != '_' && k[i] != '$') ok = false;
for (auto r : RES) if (k == r) ok = false;
return ok ? k : "\"" + k + "\""; }
void renderT(string& out, const Ty* t) {
if (t->kind == 0) out += t->prim;
else if (t->kind == 1) out += "unknown";
else if (t->kind == 3) { auto n = nameOf(t->sig); out += n.empty() ? "unknown" : n; }
else if (t->kind == 4) for (size_t i = 0; i < t->mem.size(); i++) { if (i) out += " | "; renderT(out, t->mem[i].get()); }
else if (!t->of) out += "unknown[]";
else if (t->of->kind == 4) { out += "("; renderT(out, t->of.get()); out += ")[]"; } // parens around a union element
else { renderT(out, t->of.get()); out += "[]"; } }
string jsonToTs(const string& json, const string& rootName) {
Parser p{ json }; auto root = infer(p.val(), pascal(rootName));
collect(root.get(), pascal(rootName).c_str()); // only the actual root takes rootName
string out;
for (size_t i = 0; i < g_order.size(); i++) { const Ty* o = g_order[i]; if (i) out += "\n\n";
if (o->props.empty()) { out += "interface " + nameOf(o->sig) + " {}"; continue; }
out += "interface " + nameOf(o->sig) + " {\n";
for (auto& pr : o->props) { out += " " + renderKey(pr.key) + (pr.opt ? "?" : "") + ": "; renderT(out, pr.ty.get()); out += ";\n"; }
out += "}"; }
if (root->kind != 3) { if (!g_order.empty()) out += "\n\n"; out += "type " + pascal(rootName) + " = "; renderT(out, root.get()); out += ";"; } // non-object roots emit a type alias
return out; }
int main() { // shapes dedupe by signature; array objects merge keys (name?/active? optional)
printf("%s\n", jsonToTs("{\"users\":[{\"id\":1,\"name\":\"ada\"},{\"id\":2,\"active\":true}]}", "Root").c_str()); }
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 →