JSON to Zod Schema — Ruby 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 Ruby implementation — the same logic the interactive tool runs, in a shareable, citable form.
# JSON -> Zod schema inference — Ruby port of the json-to-zod tool: parse
# with the stdlib json (Ruby hashes preserve insertion order) and run the
# recursive InferZod walk from jsonToZod.ts / go.go. Mixed-type arrays render
# z.union([every element]); one shared type renders z.array(T).
require "json"
module JsonToZod
module_function
# Reduce an arbitrary string to a usable JS identifier ("9lives" -> "_lives").
def sanitize_var_name(name)
cleaned = name.gsub(/[^A-Za-z0-9_$]/, "")
cleaned = cleaned.gsub(/\A[0-9]+/) { |digits| "_" * digits.length }
cleaned.empty? ? "schema" : cleaned
end
def infer_zod(value, indent = 2)
case value
when nil then "z.null()"
when true, false then "z.boolean()" # `when` uses ===, so 1 is not "true"
when Numeric then "z.number()"
when String then "z.string()"
when Array
return "z.array(z.unknown())" if value.empty?
types = value.map { |e| infer_zod(e, indent + 2) }
inner =
if types.uniq.one?
types.first
else # union of EVERY element
padded = types.join(",\n").split("\n").map { |l| l.empty? ? l : (" " * (indent + 2)) + l }.join("\n")
"z.union([\n#{padded}\n])" # close unpadded: pad("", indent) is ""
end
"z.array(#{inner})"
when Hash # fields in insertion order
return "z.object({})" if value.empty?
fields = value.map { |k, v| "#{' ' * (indent + 2)}#{k}: #{infer_zod(v, indent + 2)}," }
"z.object({\n#{fields.join("\n")}\n#{' ' * indent}})"
else "z.unknown()" # unreachable for valid JSON
end
end
# Never raises — parse failures are returned as {ok: false, error:}, the
# same Result shape the TS lib and the Go twin use.
def convert(json_string, root_name = "Root")
value = JSON.parse(json_string)
{ ok: true, code: "const #{sanitize_var_name(root_name)} = #{infer_zod(value)};",
error: nil }
rescue JSON::ParserError => e
{ ok: false, code: "", error: e.message }
end
end
# {"name": "Ada", "admin": true, "meta": {"roles": ["admin", 1]}}
puts JsonToZod.convert(
'{"name": "Ada", "admin": true, "meta": {"roles": ["admin", 1]}}', "User"
)[: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 →