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. C++17 port (canonical TS:
// src/lib/jsonToSql.ts; Go twin: cli/json-to-sql). No stdlib JSON codec: a
// hand-rolled JSON DOM parser (vector-of-pairs objects keep first-seen key
// order; number tokens stay raw text) feeding a multi-row INSERT builder.
// Errors return through Res — never thrown on user input.
#include <algorithm>
#include <cstring>
#include <iostream>
#include <string>
#include <utility>
#include <vector>
using std::string; using std::vector;
struct V {
enum Tag { NUL, T, F, NUM, STR, OBJ, ARR } tag = NUL;
string s; // NUM raw token ("String(n)"-safe) or STR value
vector<std::pair<string, V>> ps; // OBJ: insertion-ordered pairs
vector<V> items; // ARR
};
struct Res { bool ok; string sql; int rows; string err; };
struct Parser {
struct Bad {};
string s; size_t i = 0;
explicit Parser(string in) : s(std::move(in)) {}
[[noreturn]] static void err() { throw Bad{}; }
static bool wschar(char c) { return c == ' ' || c == '\t' || c == '\n' || c == '\r'; }
char peek() const { return i < s.size() ? s[i] : '\0'; }
char take() { if (i >= s.size()) err(); return s[i++]; }
void ws() { while (wschar(peek())) ++i; }
V value() {
ws(); char c = peek();
if (c == '{') { ++i; ws(); V v; v.tag = V::OBJ;
if (peek() == '}') { ++i; return v; }
while (true) { ws(); string k = str(); ws(); if (take() != ':') err();
v.ps.emplace_back(std::move(k), value()); ws();
char d = take(); if (d == '}') return v; if (d != ',') err(); } }
if (c == '[') { ++i; ws(); V v; v.tag = V::ARR;
if (peek() == ']') { ++i; return v; }
while (true) { v.items.push_back(value()); ws();
char d = take(); if (d == ']') return v; if (d != ',') err(); } }
if (c == '"') { V v; v.tag = V::STR; v.s = str(); return v; }
if (!s.compare(i, 4, "null")) { i += 4; return V{}; }
if (!s.compare(i, 4, "true")) { i += 4; V v; v.tag = V::T; return v; }
if (!s.compare(i, 5, "false")) { i += 5; V v; v.tag = V::F; return v; }
size_t from = i;
while (peek() && strchr("-+.eE0123456789", peek())) ++i;
if (i == from) err();
V v; v.tag = V::NUM; v.s = s.substr(from, i - from); return v; // raw token
}
string str() {
if (take() != '"') err();
string o;
while (true) { char c = take();
if (c == '"') return o;
if (c == '\\') { char e = take();
if (e == 'u') { int cp = 0; for (int k = 0; k < 4; ++k) { char h = take();
cp = cp * 16 + (h <= '9' ? h - '0' : (h | 32) - 'a' + 10); } o += (char)cp; }
else o += e == 'n' ? '\n' : e == 'r' ? '\r' : e == 't' ? '\t'
: e == 'b' ? '\b' : e == 'f' ? '\f' : e; } // '"', '\\', '/' pass through
else o += c; }
}
};
string escapeSqlString(const string& s, bool mysql) {
string o;
for (char c : s) {
if (c == '\'') o += "''";
else if (mysql && c == '\\') o += "\\\\";
else if (mysql && c == '\n') o += "\\n";
else if (mysql && c == '\r') o += "\\r";
else if (mysql && c == '\x1a') o += "\\Z"; // SUB — ends MySQL input
else o += c; // (NUL cannot occur in a C string)
}
return o;
}
string compact(const V& v) { // JSON.stringify-style re-emission
switch (v.tag) {
case V::NUL: return "null"; case V::T: return "true"; case V::F: return "false"; case V::NUM: return v.s;
case V::STR: { string o = "\"";
for (char c : v.s) { if (c == '"' || c == '\\') { o += '\\'; o += c; }
else if (c == '\n') o += "\\n"; else if (c == '\r') o += "\\r";
else if (c == '\t') o += "\\t"; else o += c; }
return o + "\""; }
case V::ARR: { string o = "["; bool first = true;
for (const auto& x : v.items) { if (!first) o += ','; first = false; o += compact(x); }
return o + "]"; }
case V::OBJ: { string o = "{"; bool first = true;
for (const auto& [k, x] : v.ps) { if (!first) o += ','; first = false;
o += compact(V{V::STR, k, {}, {}}) + ':' + compact(x); }
return o + "}"; }
}
return "";
}
string sqlLiteral(const V& v, bool mysql) {
switch (v.tag) {
case V::NUL: return "NULL";
case V::T: return mysql ? "1" : "TRUE";
case V::F: return mysql ? "0" : "FALSE";
case V::NUM: return v.s;
case V::STR: return "'" + escapeSqlString(v.s, mysql) + "'";
default: return "'" + escapeSqlString(compact(v), mysql) + "'"; // OBJ/ARR → JSON text
}
}
string ident(string n, bool mysql, bool quote) { // [^A-Za-z0-9_] -> '_', default "tbl"
for (char& c : n) { bool ok = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_';
if (!ok) c = '_'; }
if (n.empty()) n = "tbl";
if (!quote) return n;
return mysql ? "`" + n + "`" : "\"" + n + "\"";
}
// 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.
Res jsonToInsert(const string& json, const string& table, bool mysql = false, bool quote = true) {
try {
V root = Parser(json).value();
vector<const V*> rows;
if (root.tag == V::ARR) for (const auto& x : root.items) rows.push_back(&x);
else rows.push_back(&root);
if (rows.empty()) return {false, "", 0, "No rows to insert."};
for (const V* r : rows) if (r->tag != V::OBJ) return {false, "", 0, "Rows must be objects."};
vector<string> cols; // first-seen union of every row's keys
for (const V* r : rows) for (const auto& [k, x] : r->ps)
if (std::find(cols.begin(), cols.end(), k) == cols.end()) cols.push_back(k);
string sql = "INSERT INTO " + ident(table, mysql, quote) + " (";
for (size_t c = 0; c < cols.size(); ++c) { if (c) sql += ", "; sql += ident(cols[c], mysql, quote); }
sql += ") VALUES\n";
for (size_t r = 0; r < rows.size(); ++r) {
sql += " (";
for (size_t c = 0; c < cols.size(); ++c) {
if (c) sql += ", ";
const V* cell = nullptr; // absent column -> NULL
for (const auto& [k, x] : rows[r]->ps) if (k == cols[c]) cell = &x;
sql += cell ? sqlLiteral(*cell, mysql) : "NULL";
}
sql += r + 1 == rows.size() ? ");" : "),\n";
}
return {true, sql, (int)rows.size(), ""};
} catch (const Parser::Bad&) { return {false, "", 0, "Invalid JSON"}; }
}
int main() {
std::cout << jsonToInsert("[{\"id\":1,\"name\":\"O'Hara\"},{\"id\":2,\"age\":30.5,\"tags\":[\"a\",\"b\"]}]", "users").sql << "\n";
// INSERT INTO "users" ("id", "name", "age", "tags") VALUES ...
}
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 →