ZodSharp is a high-performance schema validation library for C#, ported from TypeScript Zod. It features zero-allocation validation, struct-based rules, fluent API, and source generator support for maximum performance.
- Zero-allocation validation - Minimizes allocations using structs and
Span<T> - Struct-based rules - Validation rules implemented as structs to avoid GC
- Fluent API - Fluent and extensible API similar to original Zod
- Type-safe - Strong typing with advanced C# generics
- High performance - Sub-microsecond validation times, 10x faster than reflection-based validation
- Cross-platform - Works on .NET 9.0 and .NET Standard 2.1
- Source Generators - Compile-time validator generation with
[ZodSchema]attribute - DataAnnotations Support - Automatic validation from
[Required],[StringLength],[Range], etc.
Install-Package ZodSharpdotnet add package ZodSharp<PackageReference Include="ZodSharp" Version="1.0.0" />using ZodSharp;
using ZodSharp.Core;
// String validation
var nameSchema = Z.String().Min(3).Max(50);
var result = nameSchema.Validate("John");
if (result.IsSuccess)
{
Console.WriteLine($"Valid name: {result.Value}");
}
// Number validation
var ageSchema = Z.Number().Min(0).Max(120).Int();
var ageResult = ageSchema.Validate(25.0);
// Additional number validations
var positiveSchema = Z.Number().Positive();
var negativeSchema = Z.Number().Negative();
var multipleOfSchema = Z.Number().MultipleOf(10); // Must be multiple of 10
var finiteSchema = Z.Number().Finite(); // Not Infinity
var safeSchema = Z.Number().Safe(); // Safe integer
// Email validation
var emailSchema = Z.String().Email();
var emailResult = emailSchema.Validate("user@example.com");
// URL validation
var urlSchema = Z.String().Url();
var urlResult = urlSchema.Validate("https://example.com");
// UUID validation
var uuidSchema = Z.String().Uuid();
var uuidResult = uuidSchema.Validate("550e8400-e29b-41d4-a716-446655440000");
// String transformations
var trimmedSchema = Z.String().Trim();
var upperSchema = Z.String().ToUpper();
var lowerSchema = Z.String().ToLower();
// String prefixes and suffixes
var prefixSchema = Z.String().StartsWith("https://");
var suffixSchema = Z.String().EndsWith(".com");
// Exact length
var exactLengthSchema = Z.String().Length(10);var userSchema = Z.Object()
.Field("name", Z.String().Min(1))
.Field("age", Z.Number().Min(0).Max(120))
.Field("email", Z.String().Email())
.Build();
var userData = new Dictionary<string, object?>
{
{ "name", "John Doe" },
{ "age", 30.0 },
{ "email", "john@example.com" }
};
var result = userSchema.Validate(userData);
if (result.IsSuccess)
{
var validatedUser = result.Value;
// Use validatedUser...
}var numbersSchema = Z.Array(Z.Number()).Min(1).Max(10);
var result = numbersSchema.Validate(new[] { 1.0, 2.0, 3.0 });
// Exact length
var exactLengthSchema = Z.Array(Z.String()).Length(5);
// Non-empty array
var nonEmptySchema = Z.Array(Z.String()).NonEmpty();var optionalSchema = Z.Optional(Z.String());
var result1 = optionalSchema.Validate(null); // Success
var result2 = optionalSchema.Validate("value"); // Successtry
{
var value = nameSchema.Parse("AB"); // Too short - throws
}
catch (ZodException ex)
{
Console.WriteLine($"Validation failed: {ex.Message}");
foreach (var error in ex.Errors)
{
Console.WriteLine($" - {string.Join(".", error.Path)}: {error.Message}");
}
}
// Or use SafeParse for non-throwing validation
var result = nameSchema.SafeParse("AB");
if (!result.IsSuccess)
{
foreach (var error in result.Errors)
{
Console.WriteLine($"Error: {error.Message}");
}
}ZodSharp is designed for maximum performance with zero-allocation validation and struct-based rules. Here's what makes it fast:
Typical validation times (measured on .NET 9.0, Release mode):
- Simple string validation: ~50-100 ns per validation
- Number validation: ~30-80 ns per validation
- Small arrays (10 items): ~500-800 ns per validation
- Medium objects (6 fields): ~1-2 μs per validation
- Complex objects (13 fields with nesting): ~3-5 μs per validation
Memory efficiency:
- Zero allocations for simple validations (strings, numbers, booleans)
- Minimal allocations for arrays and objects (only for error collections)
- Struct-based rules avoid GC pressure
- No reflection overhead in hot paths
ZodSharp implements several optimizations for maximum performance:
- Validation rules implemented as
structto avoid allocations - Use of
Span<T>andReadOnlySpan<T>when appropriate - Object pooling for reusable schemas
All validation rules are structs:
public readonly struct MinLengthRule : IValidationRule<string>
{
// Zero allocation validation
}Use expression trees to compile validators at runtime for maximum speed:
using ZodSharp.Expressions;
var compiled = CompiledValidator.Compile(schema);
var result = compiled(value); // Ultra-fast validationFluent API that allows schema composition:
var schema = Z.String()
.Min(3)
.Max(50)
.Email()
.Describe("User email address");We maintain comprehensive performance tests in the performance/ folder. Run them yourself:
# Run all performance benchmarks
dotnet run --project performance/performance.csproj -c Release
# Run specific test suites
dotnet run --project performance/performance.csproj -c Release --filter "*MemoryPerformanceTests*"Key performance highlights:
- 10x faster than reflection-based validation libraries
- Zero allocations for primitive validations
- Sub-microsecond validation for simple types
- Minimal GC pressure with struct-based architecture
- Scalable performance even with complex nested schemas
See the performance README for detailed benchmark results and optimization tips.
ZodSharp/
├── Core/ # Base interfaces and classes
│ ├── IZodSchema.cs
│ ├── ZodType.cs
│ ├── ValidationResult.cs
│ └── ValidationError.cs
├── Schemas/ # Schema implementations
│ ├── ZodString.cs
│ ├── ZodNumber.cs
│ ├── ZodObject.cs
│ └── ...
├── Rules/ # Validation rules (structs)
│ ├── MinLengthRule.cs
│ ├── EmailRule.cs
│ └── ...
└── Optimizations/ # Optimization helpers
└── ZeroAllocationHelpers.cs
Transform values during validation:
var schema = Z.String().Transform(s => s.ToUpper());
var result = schema.Validate("hello"); // "HELLO"Add custom validations:
var schema = Z.Number().Refine(n => n % 2 == 0, "Must be even");
var result = schema.Validate(4); // SuccessCreate recursive and circular schemas:
var categorySchema = Z.Lazy<Dictionary<string, object?>>(() =>
Z.Object()
.Field("name", Z.String())
.Field("subcategories", Z.Array(categorySchema))
.Build()
);Optimized unions with discriminator:
var union = Z.DiscriminatedUnion("type")
.Option("user", userSchema)
.Option("admin", adminSchema)
.Build();Default values when input is null:
var schema = Z.String().Default("unknown");
var result = schema.Validate(null); // "unknown"Integrated JSON validation with Newtonsoft.Json:
using ZodSharp.Json;
// Deserialize and validate from string
var result = schema.DeserializeAndValidate(jsonString);
// Deserialize and validate from stream (async)
var result2 = await schema.DeserializeAndValidateAsync(jsonStream);
// Deserialize and validate from JToken
var result3 = schema.DeserializeAndValidate(jToken);
// Create JsonConverter with validation
var converter = schema.CreateValidatingConverter();Compiled validators for maximum performance:
using ZodSharp.Expressions;
var compiled = CompiledValidator.Compile(schema);
var result = compiled(value); // Ultra-fast validationIntelligent schema caching:
using ZodSharp.Core;
var schema = SchemaCache.GetOrCreate("user", () =>
Z.Object().Field("name", Z.String()).Build()
);Generate zero-allocation validators at compile time:
using System.ComponentModel.DataAnnotations;
using ZodSharp.SourceGenerators;
[ZodSchema]
public class User
{
[Required]
[StringLength(50, MinimumLength = 3)]
public string Name { get; set; } = string.Empty;
[Required]
[Range(0, 120)]
public int Age { get; set; }
[EmailAddress]
public string? Email { get; set; }
}
// Auto-generated validator
var result = UserSchema.Validate(user);
var validated = UserSchema.Parse(user); // Throws on failure
// Composition methods
var refined = UserSchema.Refine(user, u => u.Age >= 18, "Must be adult");
var combined = UserSchema.And(user, u => u.Name.Length > 5, "Name too short");Features:
- Automatic validation from DataAnnotations attributes
- Zero-reflection, zero-allocation validators
- Composition methods (
.And(),.Or(),.Refine()) - Supports classes, structs, and records
Zero-allocation string validation using spans:
var schema = Z.String().Min(3).Max(50).Email();
var span = "user@example.com".AsSpan();
var result = schema.ValidateSpan(span);MIT License - see the LICENSE file for details.
Contributions are welcome! Please open an issue or pull request.