Skip to content

JSON to SQL INSERT — TypeScript 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 TypeScript implementation — the same logic the interactive tool runs, in a shareable, citable form.

// Pure JSON → SQL INSERT converter - no React, no DOM, deterministic.
// Escapes strings (single quotes doubled; backslash handling per dialect),
// handles null/boolean/number, serializes nested values as JSON. Never throws.

export type Dialect = 'standard' | 'mysql' | 'postgres';

export interface Options {
  table: string;
  dialect?: Dialect;
  quoteIdentifiers?: boolean;
}

export interface Result {
  ok: boolean;
  sql: string;
  rows: number;
  error: string | null;
}

export function escapeSqlString(s: string, dialect: Dialect = 'standard'): string {
  let out = s.replace(/'/g, "''");
  if (dialect === 'mysql') {
    out = out
      .replace(/\\/g, '\\\\')
      .replace(/\0/g, '\\0')
      .replace(/\n/g, '\\n')
      .replace(/\r/g, '\\r')
      .replace(/\x1a/g, '\\Z');
  }
  return out;
}

export function sqlLiteral(value: unknown, dialect: Dialect): string {
  if (value === null || value === undefined) return 'NULL';
  if (typeof value === 'boolean') {
    if (dialect === 'mysql') return value ? '1' : '0';
    return value ? 'TRUE' : 'FALSE';
  }
  if (typeof value === 'number') {
    return Number.isFinite(value) ? String(value) : 'NULL';
  }
  if (typeof value === 'string') return `'${escapeSqlString(value, dialect)}'`;
  // objects/arrays → JSON text literal
  return `'${escapeSqlString(JSON.stringify(value), dialect)}'`;
}

function quoteIdent(name: string, dialect: Dialect, quoteIdentifiers: boolean): string {
  if (!quoteIdentifiers) return name;
  return dialect === 'mysql' ? `\`${name}\`` : `"${name}"`;
}

function sanitizeIdent(name: string): string {
  const cleaned = (name || '').replace(/[^A-Za-z0-9_]/g, '_');
  return cleaned || 'tbl';
}

export function jsonToInsert(jsonString: string, opts: Options): Result {
  let data: unknown;
  try {
    data = JSON.parse(jsonString);
  } catch (e) {
    return { ok: false, sql: '', rows: 0, error: (e as Error).message };
  }

  const arr: unknown[] = Array.isArray(data) ? data : [data];
  if (arr.length === 0) return { ok: false, sql: '', rows: 0, error: 'No rows to insert.' };
  if (!arr.every((r) => r !== null && typeof r === 'object' && !Array.isArray(r))) {
    return { ok: false, sql: '', rows: 0, error: 'Rows must be objects.' };
  }

  const dialect = opts.dialect ?? 'standard';
  const quoteIdentifiers = opts.quoteIdentifiers ?? true;
  const table = quoteIdent(sanitizeIdent(opts.table), dialect, quoteIdentifiers);

  // Union of keys, first-seen order.
  const cols: string[] = [];
  for (const row of arr as Record<string, unknown>[]) {
    for (const k of Object.keys(row)) if (!cols.includes(k)) cols.push(k);
  }
  const colList = cols.map((c) => quoteIdent(c, dialect, quoteIdentifiers)).join(', ');

  const valueLists = (arr as Record<string, unknown>[]).map((row) => {
    const vals = cols.map((c) => sqlLiteral(c in row ? row[c] : null, dialect));
    return `  (${vals.join(', ')})`;
  });

  const sql = `INSERT INTO ${table} (${colList}) VALUES\n${valueLists.join(',\n')};`;
  return { ok: true, sql, rows: arr.length, error: null };
}

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 →