Skip to content

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

// json-to-sql — pure JSON → SQL INSERT converter. Java port (canonical TS:
// src/lib/jsonToSql.ts; Go twin: cli/json-to-sql). The JVM stdlib has no JSON
// codec, so — like the Rust port — this carries a small hand-rolled JSON DOM
// parser: LinkedHashMap keeps first-seen key order and number tokens stay raw
// text, so column unions and literals match the TS reference. Errors return
// through the Result record — never thrown on user input.
import java.util.*;

public final class JsonToSql {
    record Num(String raw) {}                    // raw number token — renders like JS String(n)
    record Opts(String table, String dialect, boolean quoteIdentifiers) {
        Opts(String t) { this(t, "standard", true); } }
    record Result(boolean ok, String sql, int rows, String error) {}
    // ---- tiny JSON parser: null | Boolean | Num | String | List | LinkedHashMap ----
    static final class P {
        final String s; int i;
        P(String s) { this.s = s; }
        RuntimeException bad() { return new IllegalArgumentException("Invalid JSON"); }
        Object parse() { Object v = value(); ws(); if (i != s.length()) throw bad(); return v; }
        void ws() { while (i < s.length() && Character.isWhitespace(s.charAt(i))) i++; }
        char peek() { return i < s.length() ? s.charAt(i) : '\0'; }
        char take() { if (i >= s.length()) throw bad(); return s.charAt(i++); }
        Object value() {
            ws(); char c = peek();
            if (c == '{') { i++; LinkedHashMap<String, Object> m = new LinkedHashMap<>(); ws();
                if (peek() == '}') { i++; return m; }
                while (true) { ws(); String k = (String) value(); ws(); if (take() != ':') throw bad();
                    m.put(k, value()); ws(); char d = take(); if (d == '}') return m; if (d != ',') throw bad(); } }
            if (c == '[') { i++; List<Object> l = new ArrayList<>(); ws();
                if (peek() == ']') { i++; return l; }
                while (true) { l.add(value()); ws(); char d = take(); if (d == ']') return l; if (d != ',') throw bad(); } }
            if (c == '"') return str();
            if (s.startsWith("null", i)) { i += 4; return null; }
            if (s.startsWith("true", i)) { i += 4; return Boolean.TRUE; }
            if (s.startsWith("false", i)) { i += 5; return Boolean.FALSE; }
            int from = i;
            while ("-+.eE0123456789".indexOf(peek()) >= 0 && peek() != '\0') i++;
            if (i == from) throw bad();
            return new Num(s.substring(from, i));
        }
        String str() {
            StringBuilder b = new StringBuilder();
            if (take() != '"') throw bad();
            while (true) { char c = take();
                if (c == '"') return b.toString();
                if (c == '\\') { char e = take();
                    b.append(switch (e) {
                        case '"', '\\', '/' -> e; case 'b' -> '\b'; case 'f' -> '\f';
                        case 'n' -> '\n'; case 'r' -> '\r'; case 't' -> '\t';
                        case 'u' -> { int cp = 0; for (int k = 0; k < 4; k++) cp = cp * 16 + Character.digit(take(), 16); yield (char) cp; }
                        default -> throw bad(); }); }
                else b.append(c); }
        }
    }
    // ---- SQL rendering ----
    static String escapeSqlString(String s, String d) {
        String o = s.replace("'", "''");
        if (d.equals("mysql")) o = o.replace("\\", "\\\\").replace("\0", "\\0")
                .replace("\n", "\\n").replace("\r", "\\r").replace("\u001a", "\\Z");
        return o;
    }
    static String sqlLiteral(Object v, String d) {
        if (v == null) return "NULL";
        if (v instanceof Boolean b) return d.equals("mysql") ? (b ? "1" : "0") : (b ? "TRUE" : "FALSE");
        if (v instanceof Num n) return n.raw();
        if (v instanceof String s) return "'" + escapeSqlString(s, d) + "'";
        return "'" + escapeSqlString(compact(v), d) + "'"; // List/Map → JSON text
    }
    static String compact(Object v) { // JSON.stringify-style re-emission
        if (v == null) return "null";
        if (v instanceof Boolean b) return b.toString();
        if (v instanceof Num n) return n.raw();
        if (v instanceof String s) return "\"" + s.replace("\\", "\\\\").replace("\"", "\\\"")
                .replace("\n", "\\n").replace("\r", "\\r").replace("\t", "\\t") + "\"";
        if (v instanceof List<?> l) { StringJoiner j = new StringJoiner(",", "[", "]");
            for (Object x : l) j.add(compact(x)); return j.toString(); }
        @SuppressWarnings("unchecked") Map<String, Object> m = (Map<String, Object>) v;
        StringJoiner j = new StringJoiner(",", "{", "}");
        for (Map.Entry<String, Object> e : m.entrySet()) j.add(compact(e.getKey()) + ":" + compact(e.getValue()));
        return j.toString();
    }
    static String quoteIdent(String n, String d, boolean q) { return !q ? n : d.equals("mysql") ? "`" + n + "`" : "\"" + n + "\""; }
    static String sanitizeIdent(String n) { String c = n.replaceAll("[^A-Za-z0-9_]", "_"); return c.isEmpty() ? "tbl" : c; }
    /** 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 Result jsonToInsert(String json, Opts opts) {
        Object data;
        try { data = new P(json).parse(); }
        catch (IllegalArgumentException e) { return new Result(false, "", 0, e.getMessage()); }
        List<Object> rows = data instanceof List<?> l ? new ArrayList<>(l) : new ArrayList<>(Collections.singletonList(data));
        if (rows.isEmpty()) return new Result(false, "", 0, "No rows to insert.");
        for (Object r : rows) if (!(r instanceof LinkedHashMap)) return new Result(false, "", 0, "Rows must be objects.");
        String d = opts.dialect(); boolean q = opts.quoteIdentifiers();
        String table = quoteIdent(sanitizeIdent(opts.table()), d, q);
        List<String> cols = new ArrayList<>(); // first-seen union of every row's keys
        for (Object r : rows) for (String k : ((Map<String, Object>) r).keySet()) if (!cols.contains(k)) cols.add(k);
        StringBuilder sql = new StringBuilder("INSERT INTO ").append(table).append(" (")
                .append(String.join(", ", cols.stream().map(c -> quoteIdent(c, d, q)).toList())).append(") VALUES\n");
        for (int r = 0; r < rows.size(); r++) {
            Map<String, Object> row = (Map<String, Object>) rows.get(r);
            StringJoiner vals = new StringJoiner(", ");
            for (String c : cols) vals.add(sqlLiteral(row.get(c), d)); // absent → null → NULL
            sql.append("  (").append(vals).append(")").append(r == rows.size() - 1 ? ";" : ",\n");
        }
        return new Result(true, sql.toString(), rows.size(), null);
    }
    public static void main(String[] args) {
        System.out.println(jsonToInsert("[{\"id\":1,\"name\":\"O'Hara\"},{\"id\":2,\"age\":30.5,\"tags\":[\"a\",\"b\"]}]",
            new Opts("users")).sql()); // 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 →