JSON to Zod Schema — Swift 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 Swift implementation — the same logic the interactive tool runs, in a shareable, citable form.
// JSON -> Zod schema inference — Swift port of the json-to-zod tool: the
// recursive InferZod walk from jsonToZod.ts / go.go. Dictionary does not
// guarantee key order, so object nodes carry labeled tuples in textual order
// (the Go twin's order-preserving decoder fills the same shape).
import Foundation
indirect enum JVal {
case null
case bool(Bool)
case number(Double)
case string(String)
case array([JVal])
case object([(key: String, value: JVal)])
}
func inferZod(_ value: JVal, indent: Int = 2) -> String {
switch value {
case .null: return "z.null()"
case .bool: return "z.boolean()"
case .number: return "z.number()"
case .string: return "z.string()"
case .array(let items):
guard !items.isEmpty else { return "z.array(z.unknown())" }
let types = items.map { inferZod($0, indent: indent + 2) }
let inner: String
if Set(types).count == 1 { // one shared element type
inner = types[0]
} else { // union of EVERY element
let padded = types.joined(separator: ",\n")
.split(separator: "\n", omittingEmptySubsequences: false)
.map { line -> String in // pad EVERY line
line.isEmpty ? "" : String(repeating: " ", count: indent + 2) + String(line)
}
.joined(separator: "\n")
inner = "z.union([\n" + padded + "\n])" // close unpadded: pad("", ind) is ""
}
return "z.array(\(inner))"
case .object(let fields): // fields in textual order
guard !fields.isEmpty else { return "z.object({})" }
let lines = fields.map { key, v in
String(repeating: " ", count: indent + 2)
+ "\(key): \(inferZod(v, indent: indent + 2)),"
}
return "z.object({\n" + lines.joined(separator: "\n") + "\n"
+ String(repeating: " ", count: indent) + "})"
}
}
// Reduce an arbitrary string to a usable JS identifier ("9lives" -> "_lives").
func sanitizeVarName(_ name: String) -> String {
var cleaned = String(name.compactMap {
($0.isASCII && $0.isLetter) || ($0.isASCII && $0.isNumber)
|| $0 == "_" || $0 == "$" ? $0 : nil
})
while let first = cleaned.first, first.isNumber {
cleaned = "_" + cleaned.dropFirst() // each leading digit -> "_"
}
return cleaned.isEmpty ? "schema" : cleaned
}
// {"name": "Ada", "admin": true, "meta": {"roles": ["admin", 1]}}
let doc: JVal = .object([
("name", .string("Ada")),
("admin", .bool(true)),
("meta", .object([("roles", .array([.string("admin"), .number(1)]))])),
])
print("const User = \(inferZod(doc));")
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 →