Union of Objects

Discriminated Unions

What are Discriminated Unions?

Discriminated unions (also called tagged unions) allow you to create a field that can be one of several object types. Each type is identified by a discriminator field (commonly called 'type'), which makes it easy to determine which variant you're working with.

Basic Syntax

Create a discriminated union by specifying the discriminator field name as the first argument:

const contentBlockSchema = s.union(
  "type", // The discriminator field
  s.object({
    type: s.literal("text"),
    content: s.richtext(),
  }),
  s.object({
    type: s.literal("image"),
    src: s.image(),
    alt: s.string(),
  }),
  s.object({
    type: s.literal("video"),
    url: s.string(),
    thumbnail: s.image().nullable(),
  })
);

Using in Content

When you use a discriminated union in your content, Val Studio will show a dropdown to select which variant to use:

const pageSchema = s.object({
  blocks: s.array(contentBlockSchema),
});

export default c.define("/content/page.val.ts", pageSchema, {
  blocks: [
    {
      type: "text",
      content: [{ tag: "p", children: ["Hello world"] }],
    },
    {
      type: "image",
      src: c.image("/public/val/hero.jpg", {
        width: 1200,
        height: 800,
        alt: "Hero image",
      }),
      alt: "A beautiful hero image",
    },
    {
      type: "video",
      url: "https://example.com/video.mp4",
      thumbnail: null,
    },
  ],
});

Rendering Discriminated Unions

In your React components, you can use TypeScript's type narrowing to handle each variant:

function ContentBlock({ block }: { block: ContentBlock }) {
  switch (block.type) {
    case "text":
      return <ValRichText>{block.content}</ValRichText>;
    case "image":
      return <ValImage src={block.src} alt={block.alt} />;
    case "video":
      return (
        <video src={block.url} poster={block.thumbnail || undefined} />
      );
  }
}

Use Cases

Discriminated unions are perfect for:

  • Content blocks with different types

  • Form fields with different input types

  • Component variants with different props

  • Any content that can take multiple shapes