JSON to Zod Schema — C# 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 C# implementation — the same logic the interactive tool runs, in a shareable, citable form.
// JSON -> Zod schema inference — C# (.NET 8, top-level statements) port of
// the json-to-zod tool: parse with System.Text.Json (JsonDocument keeps
// property order) and run the recursive InferZod walk from jsonToZod.ts /
// go.go. Never throws — parse failures come back as (ok, code, error).
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Text.RegularExpressions;
string Infer(JsonElement e, int indent = 2)
{
switch (e.ValueKind)
{
case JsonValueKind.Null: return "z.null()";
case JsonValueKind.True:
case JsonValueKind.False: return "z.boolean()";
case JsonValueKind.Number: return "z.number()";
case JsonValueKind.String: return "z.string()";
case JsonValueKind.Array:
var items = e.EnumerateArray().ToList();
if (items.Count == 0) return "z.array(z.unknown())";
var types = items.Select(i => Infer(i, indent + 2)).ToList();
var distinct = types.Distinct().ToList();
string inner = distinct.Count == 1 ? distinct[0]
: "z.union([\n" + string.Join("\n", // union of EVERY element
string.Join(",\n", types).Split('\n')
.Select(l => l.Length == 0 ? l : new string(' ', indent + 2) + l))
+ "\n])"; // close unpadded — pad("", indent) is "" in TS
return $"z.array({inner})";
case JsonValueKind.Object:
var fields = e.EnumerateObject().ToList(); // order preserved
if (fields.Count == 0) return "z.object({})";
var body = string.Join("\n", fields.Select(f =>
$"{new string(' ', indent + 2)}{f.Name}: {Infer(f.Value, indent + 2)},"));
return $"z.object({{\n{body}\n{new string(' ', indent)}}})";
default: return "z.unknown()"; // unreachable for valid JSON
}
}
// Reduce an arbitrary string to a usable JS identifier ("9lives" -> "_lives").
string SanitizeVarName(string name)
{
string cleaned = new string(name
.Where(c => char.IsAsciiLetterOrDigit(c) || c == '_' || c == '$').ToArray());
cleaned = Regex.Replace(cleaned, "^[0-9]+", m => new string('_', m.Length));
return cleaned.Length == 0 ? "schema" : cleaned;
}
(bool Ok, string Code, string? Error) JsonToZod(string json, string rootName = "Root")
{
try
{
using var doc = JsonDocument.Parse(json);
return (true, $"const {SanitizeVarName(rootName)} = {Infer(doc.RootElement)};", null);
}
catch (JsonException ex) { return (false, "", ex.Message); }
}
// {"name": "Ada", "admin": true, "meta": {"roles": ["admin", 1]}}
Console.WriteLine(JsonToZod(
"""{"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 →