Unions and Discriminated Unions
Unions and Discriminated Unions
Section titled “Unions and Discriminated Unions”Untyped union
Section titled “Untyped union”ZodUnion (namespace ZodSharp.Schemas) tries each option in order and returns the first success.
using ZodSharp;
var schema = Z.Union(Z.String(), Z.Number(), Z.Boolean());var result = schema.Validate(42.0); // matches Z.Number()When no option matches, a single invalid_union error is produced ("Value does not match any of the union options") whose Parameters["errors"] carries every option’s errors.
Typed union
Section titled “Typed union”ZodTypedUnion<T1, T2> dispatches on runtime type (is T1 / is T2) and produces a Union<T1, T2> result value.
var schema = Z.Union(Z.String(), Z.Number());var result = schema.Validate(42.0);
if (result.IsSuccess) result.Value.Match( str => Console.WriteLine($"string: {str}"), num => Console.WriteLine($"number: {num}"));Union<T1, T2> and Union<T1, T2, T3>
Section titled “Union<T1, T2> and Union<T1, T2, T3>”The Union<...> value type (namespace ZodSharp.Unions) is the result of a typed union:
Create(T1)/Create(T2)(and a three-case variant) — tagged construction.- Implicit conversions from the case types.
int Tagandobject Value(Valuethrows if uninitialized).TryGetValue(out T1)/TryGetValue(out T2).Match(Func<T1, TResult>, Func<T2, TResult>)andSwitch(Action<T1>, Action<T2>).==/!=,Equals,GetHashCode,ToString.
Native unions (.NET 11+)
Section titled “Native unions (.NET 11+)”When targeting net11.0 or later, Z.NativeUnion<T1, T2> returns a native C# 15 union
(ZodSharp.Unions.NativeUnion<T1, T2>) instead of Union<T1, T2>:
var schema = Z.NativeUnion(Z.String().Min(1), Z.Object().Build());var result = schema.Validate("hello");
if (result.IsSuccess){ var length = result.Value switch { string s => s.Length, Dictionary<string, object?> o => o.Count, _ => -1, };}Prefer this when both option types are reference types: the native union is allocation-free and
supports exhaustive pattern matching. Value-type cases box, so keep using Z.Union<T1, T2> (the
hand-rolled Union<T1, T2>) when a case is a value type, or when you need
Tag/Match/Switch/TryGetValue/equality. The analyzer reports ZODSGEN041 (Info) and offers a
code fix when a reference-type-only Z.Union can be switched to Z.NativeUnion.
Discriminated union
Section titled “Discriminated union”ZodDiscriminatedUnion dispatches on a discriminator value read from the input — a dictionary key or a public instance property — resolved case-insensitively.
var union = Z.DiscriminatedUnion("type") .Option("user", userSchema) .Option("admin", adminSchema) .Build();
var result = union.Validate(new Dictionary<string, object?>{ { "type", "user" }, { "name", "John" }});Failures:
- No discriminator present →
missing_discriminator. - Value not among the options →
invalid_discriminatorlisting the expected values. nullinput →invalid_type.
The builder (ZodDiscriminatedUnionBuilder) accepts untyped IZodSchema<object, object> options via Option(string value, IZodSchema<object, object> schema) and typed options via Option<T>(string value, IZodSchema<T, T> schema), which wrap the schema for coercion and null handling.
Intersection
Section titled “Intersection”ZodIntersection<T> (created with Z.Intersection<T>(left, right) or .And(other)) succeeds only when both schemas validate; failures merge both error sets.
var schema = Z.String().Min(3).And(Z.String().Max(10));