Skip to content

Unions and Discriminated Unions

Preview ZodSharp Reviewed 2026-09-16 purview-dev/zodsharp aspnetcore c-sharp csharp dotnet json-schema newtonsoft-json schema source-generator system-text-json validation zero-allocation zod

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.

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}"));

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 Tag and object Value (Value throws if uninitialized).
  • TryGetValue(out T1) / TryGetValue(out T2).
  • Match(Func<T1, TResult>, Func<T2, TResult>) and Switch(Action<T1>, Action<T2>).
  • == / !=, Equals, GetHashCode, ToString.

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_discriminator listing the expected values.
  • null input → 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.

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));