Skip to content

JSON to Zod Schema — Java source

Generate Zod validation schemas from JSON. Infers z.string, z.number, z.boolean, z.object, z.array, z.null, and z.union for mixed arrays.

This is the Java implementation — the same logic the interactive tool runs, in a shareable, citable form.

// JSON -> Zod schema inference — Java (17+) port of the json-to-zod tool:
// the recursive InferZod walk from jsonToZod.ts / go.go. Object nodes are
// LinkedHashMap so textual key order survives; parse-shaped failures are
// returned, never thrown, mirroring the TS Result.
import java.util.*;

public final class JsonToZod {
    @SuppressWarnings("unchecked")
    static String infer(Object value, int indent) {
        if (value == null) return "z.null()";
        if (value instanceof Boolean) return "z.boolean()"; // Boolean, before Number
        if (value instanceof Number) return "z.number()";
        if (value instanceof String) return "z.string()";
        if (value instanceof List) {
            List<Object> arr = (List<Object>) value;
            if (arr.isEmpty()) return "z.array(z.unknown())";
            List<String> types = new ArrayList<>();
            for (Object e : arr) types.add(infer(e, indent + 2));
            boolean same = true;                    // one shared element type
            for (String t : types) if (!t.equals(types.get(0))) same = false;
            if (same) return "z.array(" + types.get(0) + ")";
            String pad2 = " ".repeat(indent + 2);   // union of EVERY element
            StringBuilder u = new StringBuilder("z.array(z.union([\n");
            for (String line : String.join(",\n", types).split("\n", -1))
                u.append(line.isEmpty() ? "" : pad2).append(line).append('\n');
            return u + "]))"; // close unpadded — pad("", indent) is "" in TS
        }
        Map<String, Object> obj = (Map<String, Object>) value;
        if (obj.isEmpty()) return "z.object({})";
        String pad2 = " ".repeat(indent + 2);       // fields in textual order
        StringBuilder out = new StringBuilder("z.object({\n");
        for (String key : obj.keySet())
            out.append(pad2).append(key).append(": ")
               .append(infer(obj.get(key), indent + 2)).append(",\n");
        return out + " ".repeat(indent) + "})";
    }

    // Reduce an arbitrary string to a usable JS identifier ("9lives" -> "_lives").
    static String sanitizeVarName(String name) {
        StringBuilder b = new StringBuilder();
        for (char c : name.toCharArray())
            if (Character.isLetterOrDigit(c) || c == '_' || c == '$') b.append(c);
        String cleaned = b.toString();
        int lead = 0;
        while (lead < cleaned.length() && Character.isDigit(cleaned.charAt(lead))) lead++;
        cleaned = (lead > 0 ? "_".repeat(lead) : "") + cleaned.substring(lead);
        return cleaned.isEmpty() ? "schema" : cleaned;
    }

    static Map<String, Object> convert(Object tree, String rootName) {
        return Map.of("ok", true, "code",
            "const " + sanitizeVarName(rootName) + " = " + infer(tree, 2) + ";");
    }

    public static void main(String[] args) {
        // {"name": "Ada", "admin": true, "meta": {"roles": ["admin", 1]}}
        Map<String, Object> meta = new LinkedHashMap<>();
        meta.put("roles", List.of("admin", 1));
        Map<String, Object> doc = new LinkedHashMap<>();
        doc.put("name", "Ada"); doc.put("admin", true); doc.put("meta", meta);
        System.out.println(convert(doc, "User").get("code"));
    }
}

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 →