JSON to Zod Schema — Kotlin 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 Kotlin implementation — the same logic the interactive tool runs, in a shareable, citable form.
// JSON -> Zod schema inference — Kotlin port of the json-to-zod tool: the
// recursive InferZod walk from jsonToZod.ts / go.go. Objects are JObj field
// lists so textual key order survives (the tool's parity requirement).
class JObj(vararg val fields: Pair<String, Any?>)
fun inferZod(value: Any?, indent: Int = 2): String = when (value) {
null -> "z.null()"
is Boolean -> "z.boolean()"
is Number -> "z.number()"
is String -> "z.string()"
is List<*> -> {
if (value.isEmpty()) "z.array(z.unknown())"
else {
val types = value.map { inferZod(it, indent + 2) }
val inner = if (types.distinct().size == 1) types.first()
else {
val padded = types.joinToString(",\n") // union of EVERY element
.split("\n").joinToString("\n") { // pad EVERY line
if (it.isEmpty()) it else " ".repeat(indent + 2) + it
}
"z.union([\n$padded\n])" // close unpadded: pad("", indent) is ""
}
"z.array($inner)"
}
}
is JObj -> {
if (value.fields.isEmpty()) "z.object({})"
else buildString { // fields in textual order
append("z.object({\n")
for ((key, v) in value.fields)
append(" ".repeat(indent + 2)).append(key).append(": ")
.append(inferZod(v, indent + 2)).append(",\n")
append(" ".repeat(indent)).append("})")
}
}
else -> "z.unknown()" // unreachable for valid JSON
}
// Reduce an arbitrary string to a usable JS identifier ("9lives" -> "_lives").
fun sanitizeVarName(name: String): String {
val cleaned = name.filter { it.isLetterOrDigit() || it == '_' || it == '$' }
.replace(Regex("^[0-9]+")) { m -> "_".repeat(m.value.length) }
return cleaned.ifEmpty { "schema" }
}
fun jsonToZod(tree: Any?, rootName: String = "Root"): Result<String> =
runCatching { "const ${sanitizeVarName(rootName)} = ${inferZod(tree)};" }
fun main() {
// {"name": "Ada", "admin": true, "meta": {"roles": ["admin", 1]}}
val doc = JObj(
"name" to "Ada",
"admin" to true,
"meta" to JObj("roles" to listOf("admin", 1))
)
println(jsonToZod(doc, "User").getOrThrow())
}
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 →