Porting Zod to C#: ZodSharp – A Zero-Allocation, High-Performance Schema Validation Library for .NET
Source: Dev.to
Introduction
As a .NET developer, I’ve long been frustrated by one persistent pain point: most validation libraries rely heavily on reflection, leading to slow performance and constant allocations. Whether it’s validating API inputs, forms, or data pipelines, that overhead adds up — especially in high‑throughput scenarios.
Then I discovered Zod — the gold standard for schema validation in the TypeScript world, created by colinhacks. Its fluent API, full type inference, excellent error messages, and zero‑dependency design felt like magic. But there was nothing quite like it in C#.
So I built ZodSharp — a complete, from‑scratch rewrite of Zod tailored for modern .NET, with a relentless focus on performance, zero‑allocation, and developer experience.
Why Zod Became the Standard in TypeScript
- Fluent, chainable API that’s a joy to write
- Full type safety with automatic inference
- Powerful composition for complex schemas
- Rich transforms and refinements
- Clear, structured error reporting
- Zero runtime dependencies
In .NET, we have solid options like FluentValidation, but they lean on reflection and expressions — great for flexibility, but costly in performance‑critical paths. ZodSharp brings that same elegant developer experience to C# — without the allocation tax.
Not a Simple Port — A Performance‑First Rewrite
While staying faithful to Zod’s public API and semantics, nearly every internal detail was re‑engineered for .NET’s strengths.
Zero‑Allocation: The Core Design Goal
The #1 priority: validation should produce zero garbage in hot paths. Reflection‑based validation creates objects, closures, and temporary collections on every call. ZodSharp eliminates that entirely.
Key techniques
- Validation rules as structs
public readonly struct MinLengthRule : IValidationRule
{
private readonly int _min;
public MinLengthRule(int min) => _min = min;
public bool IsValid(in string value) => value.Length >= _min;
}
- Immutable collections with minimal overhead (
ImmutableArray,ImmutableDictionary) ValidationResultas a struct — no heap allocation on success or failureArrayPoolfor any temporary buffers- Manual loops instead of LINQ in performance‑critical sections
SpanandReadOnlySpanfor string operations with zero copies
Fluent API — As Close to Zod as Possible
The API feels instantly familiar to Zod users:
// Almost identical to Zod TS
var schema = Z.String()
.Min(3)
.Max(50)
.Email()
.Trim()
.ToLower();
Object schemas:
var userSchema = Z.Object()
.Field("name", Z.String().Min(1))
.Field("age", Z.Number().Min(0).Max(120))
.Field("email", Z.String().Email())
.Build();
Transforms, refinements, unions, optionals, literals — all supported.
Source Generators & DataAnnotations Integration
One of the biggest .NET‑specific wins: compile‑time schema generation.
[ZodSchema]
public class User
{
[Required, StringLength(50, MinimumLength = 3)]
public string Name { get; set; } = string.Empty;
[Range(0, 120)]
public int Age { get; set; }
[EmailAddress]
public string Email { get; set; } = string.Empty;
}
// Usage
var result = UserSchema.Validate(new User { /* ... */ });
No runtime reflection needed.
Project Architecture Overview
ZodSharp/
├── src/ZodSharp/
│ ├── Core/ → Interfaces, base classes, results, errors
│ ├── Schemas/ → String, Number, Object, Array, Union, etc.
│ ├── Rules/ → Struct‑based validation rules
│ ├── Expressions/ → Compiled validators via Expression Trees
│ ├── Json/ → Newtonsoft.Json integration
│ ├── Optimizations/ → Zero‑allocation helpers
│ └── Z.cs → Static factory (Z.String(), Z.Object(), etc.)
├── src/ZodSharp.SourceGenerators/
│ └── ZodSchemaGenerator.cs → [ZodSchema] source generator
├── example/ → Full usage samples
└── README.md
Rough Performance Wins
Early benchmarks show dramatic improvements over typical reflection‑based validation:
| Scenario | Reflection‑Based | ZodSharp | Gain |
|---|---|---|---|
| Simple string validation | ~0.15 ms | ~0.01 ms | ~15× faster |
| Complex object validation | ~0.8 ms | ~0.05 ms | ~16× faster |
| Allocations per validation | Multiple | Zero | Eliminated |
The gap widens under load.
Practical Use Cases
ZodSharp shines anywhere validation performance matters:
- High‑throughput APIs (REST, gRPC, GraphQL)
- Microservices with frequent input validation
- Real‑time systems
- Data ingestion pipelines
- Desktop/mobile apps with complex forms
- Anywhere you want Zod‑like developer experience without the runtime cost
What’s Already Implemented
- Fluent schema building
- Full primitive support (string, number, boolean, array, object, union, literal)
- Transforms (
.Trim(),.ToLower(), etc.) - Refinements (custom validation)
- Optional / nullable wrappers
- Discriminated unions
- Lazy/recursive schemas
- Source generator with DataAnnotations
- Newtonsoft.Json integration
- Advanced string rules (
.Url(),.Uuid(),.Email(),.StartsWith()…) - Advanced number rules (
.Positive(),.MultipleOf(),.Finite()…)
What’s Next?
- Public benchmark suite
- ASP.NET Core model binding integration
- Entity Framework validation hooks
- More JSON serializer support (
System.Text.Json) - Enhanced error formatting
- Community contributions
Final Thoughts
Building ZodSharp was incredibly rewarding. It wasn’t just about bringing Zod to C# — it was about solving a real, widespread pain in the .NET ecosystem: fast, type‑safe, zero‑overhead validation.
If you’ve ever groaned at reflection slowdowns or wished for a more modern validation experience in C#, give ZodSharp a try. Feedback, stars, issues, and PRs are very welcome!
Thanks for reading! 🚀