Skip to content

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

// json-to-typescript — infer TypeScript interfaces from any JSON value. Java
// port (canonical TS: src/lib/json-to-typescript/; Go twin: cli/json-to-typescript).
// No stdlib JSON, so a minimal order-preserving parser (objects as
// LinkedHashMap) feeds the condensed core: 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.
import java.util.*;

class JsonToTs { // not public: the display filename (java.java) differs from the class name
    static String in; static int i;
    static void ws() { while (i < in.length() && Character.isWhitespace(in.charAt(i))) i++; }
    static String str() { // scan a quoted string, decoding escapes
        StringBuilder b = new StringBuilder(); i++;
        for (boolean esc = false;;) { char c = in.charAt(i++);
            if (!esc && c == '\\') { esc = true; continue; }
            if (esc && c == 'u') { int cp = 0; for (int k = 0; k < 4; k++) { char h = in.charAt(i++); cp = cp * 16 + (h <= '9' ? h - '0' : (h | 32) - 'a' + 10); } b.append((char) cp); }
            else if (esc) b.append(c == 'n' ? '\n' : c == 't' ? '\t' : c);
            else if (c == '"') return b.toString();
            else b.append(c);
            esc = false; } }
    static Object value() { ws(); char c = in.charAt(i);
        if (c == '{' || c == '[') { boolean obj = c == '{'; i++; ws();
            Map<String, Object> m = new LinkedHashMap<>(); List<Object> l = new ArrayList<>();
            if (in.charAt(i) != (obj ? '}' : ']')) for (;;) {
                if (obj) { String k = str(); ws(); i++; m.put(k, value()); } else l.add(value());
                ws(); char d = in.charAt(i++); if (d == ',') { ws(); continue; } break; } else i++;
            return obj ? m : l; }
        if (c == '"') { str(); return ""; } // value text unused: only the kind matters
        if (in.startsWith("true", i) || in.startsWith("false", i)) { i += in.charAt(i) == 't' ? 4 : 5; return Boolean.TRUE; }
        if (in.startsWith("null", i)) { i += 4; return null; }
        int st = i; while (i < in.length() && "+-.eE0123456789".indexOf(in.charAt(i)) >= 0) i++;
        return Double.parseDouble(in.substring(st, i)); }
    // Type nodes: prim set -> primitive ("?" = unknown); props -> object; arr -> array; uni -> union
    static class N { String prim, sig, hint; boolean arr; N of; List<N> uni; List<P> props; }
    static class P { String key; N ty; boolean opt; P(String k, N t, boolean o) { key = k; ty = t; opt = o; } }
    static N prim(String ts) { N n = new N(); n.prim = n.sig = ts; return n; }
    static N arrN(N of) { N n = new N(); n.arr = true; n.of = of; n.sig = of != null ? "[" + of.sig + "]" : "[]"; return n; }
    static N objN(List<P> props, String hint) { N n = new N(); n.props = props; n.hint = hint; StringBuilder s = new StringBuilder("{");
        for (int q = 0; q < props.size(); q++) s.append(q > 0 ? ";" : "").append(props.get(q).key).append(':').append(props.get(q).ty.sig);
        n.sig = s.append('}').toString(); return n; }
    static String pascal(String k) { StringBuilder b = new StringBuilder(); boolean up = true; // `user_id` -> `UserId`; digit escapes with N
        for (char c : k.toCharArray()) if (!Character.isLetterOrDigit(c)) up = true; else { b.append(up ? Character.toUpperCase(c) : c); up = false; }
        if (b.length() == 0) b.append("Item");
        if (Character.isDigit(b.charAt(0))) b.insert(0, 'N');
        return b.toString(); }
    static String singular(String n) { return n.length() > 1 && n.endsWith("s") && !n.endsWith("ss") ? n.substring(0, n.length() - 1) : n + "Item"; }
    static List<N> dedupe(List<N> nodes) { List<N> d = new ArrayList<>();
        for (N n : nodes) { boolean dup = false; for (N u : d) if (u.sig.equals(n.sig)) dup = true; if (!dup) d.add(n); } return d; }
    static N combine(List<N> nodes) { // condensed: objects merge; the rest dedupe to single-or-union
        if (nodes.isEmpty()) return prim("?");
        boolean objs = true; for (N n : nodes) if (n.props == null) objs = false;
        if (objs) return merge(nodes);
        List<N> d = dedupe(nodes);
        if (d.size() == 1) return d.get(0);
        N u = new N(); u.uni = d; StringBuilder s = new StringBuilder("(");
        for (int q = 0; q < d.size(); q++) s.append(q > 0 ? "|" : "").append(d.get(q).sig);
        u.sig = s.append(')').toString(); return u; }
    static N merge(List<N> objs) { // union of keys; a key absent from some element becomes optional
        LinkedHashSet<String> keys = new LinkedHashSet<>();
        for (N o : objs) for (P p : o.props) keys.add(p.key);
        List<P> props = new ArrayList<>();
        for (String k : keys) { List<N> types = new ArrayList<>();
            for (N o : objs) for (P p : o.props) if (p.key.equals(k)) types.add(p.ty);
            props.add(new P(k, combine(types), types.size() < objs.size())); }
        return objN(props, objs.get(0).hint); }
    static N infer(Object v, String hint) {
        if (v == null) return prim("null");
        if (v instanceof Boolean) return prim("boolean");
        if (v instanceof Double) return prim("number");
        if (v instanceof String) return prim("string");
        if (v instanceof List) { List<?> a = (List<?>) v;
            if (a.isEmpty()) return arrN(null);
            String eh = singular(hint); List<N> el = new ArrayList<>();
            for (Object e : a) el.add(infer(e, eh));
            return arrN(combine(el)); }
        Map<?, ?> m = (Map<?, ?>) v; List<P> props = new ArrayList<>();
        for (Map.Entry<?, ?> e : m.entrySet()) props.add(new P((String) e.getKey(), infer(e.getValue(), hint + pascal((String) e.getKey())), false));
        return objN(props, hint); }
    static List<String> gSigs = new ArrayList<>(), gNames = new ArrayList<>(); static List<N> gOrder = new ArrayList<>();
    static String nameOf(String sig) { int x = gSigs.indexOf(sig); return x < 0 ? null : gNames.get(x); }
    static boolean used(String n) { return gNames.contains(n); }
    static void collect(N t, String forced) { // first-seen naming; collisions get 2, 3, ...
        if (t.props != null) { if (nameOf(t.sig) == null) { String cand = forced != null ? forced : t.hint;
                if (used(cand)) { int i = 2; while (used(cand + i)) i++; cand = cand + i; }
                gSigs.add(t.sig); gNames.add(cand); gOrder.add(t); }
            for (P p : t.props) collect(p.ty, null); }
        else if (t.arr && t.of != null) collect(t.of, null);
        else if (t.uni != null) for (N m : t.uni) collect(m, null); }
    static final String[] RES = { "class", "const", "default", "function", "interface", "let", "new", "null", "return", "this", "type", "var", "boolean", "number", "string", "unknown" };
    static String renderKey(String k) { // bareword when legal, else a quoted string key (no escapes needed here)
        boolean ok = Character.isLetter(k.charAt(0)) || k.charAt(0) == '_' || k.charAt(0) == '$';
        for (int i = 1; ok && i < k.length(); i++) ok = Character.isLetterOrDigit(k.charAt(i)) || k.charAt(i) == '_' || k.charAt(i) == '$';
        for (String r : RES) if (r.equals(k)) ok = false;
        return ok ? k : "\"" + k + "\""; }
    static void renderT(StringBuilder b, N t) {
        if (t.prim != null) b.append("?".equals(t.prim) ? "unknown" : t.prim);
        else if (t.props != null) b.append(nameOf(t.sig) == null ? "unknown" : nameOf(t.sig));
        else if (t.uni != null) for (int i = 0; i < t.uni.size(); i++) { if (i > 0) b.append(" | "); renderT(b, t.uni.get(i)); }
        else if (t.of == null) b.append("unknown[]");
        else if (t.of.uni != null) { b.append('('); renderT(b, t.of); b.append(")[]"); } // parens around a union element
        else { renderT(b, t.of); b.append("[]"); } }
    static String jsonToTs(String json, String rootName) {
        in = json; i = 0; N root = infer(value(), pascal(rootName)); collect(root, pascal(rootName));
        StringBuilder b = new StringBuilder();
        for (int q = 0; q < gOrder.size(); q++) { N o = gOrder.get(q); if (q > 0) b.append("\n\n");
            b.append("interface ").append(nameOf(o.sig));
            if (o.props.isEmpty()) { b.append(" {}"); continue; }
            b.append(" {\n");
            for (P p : o.props) { b.append("  ").append(renderKey(p.key)).append(p.opt ? "?" : "").append(": "); renderT(b, p.ty); b.append(";\n"); }
            b.append('}'); }
        if (root.props == null) { if (!gOrder.isEmpty()) b.append("\n\n"); b.append("type ").append(pascal(rootName)).append(" = "); renderT(b, root); b.append(';'); }
        return b.toString(); }
    public static void main(String[] args) { // shapes dedupe; array objects merge keys (name?/active? optional)
        System.out.println(jsonToTs("{\"users\":[{\"id\":1,\"name\":\"ada\"},{\"id\":2,\"active\":true}]}", "Root")); }
}

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 →