CodeSampleX

Пример

zod 3.24.2: Construct and validate discriminated union schemas in Zod with literal and enum discriminators, optionsMap indexing, and discriminator issue reporting

Проверенный пример — npm zod 3.24.2: Construct and validate discriminated union schemas in Zod with literal and enum discriminators, optionsMap indexing, and…

sha256:32f249dd8e24038855d56253549bcee08afe8de71af21aab61c0b489257727db

Эта сеть предлагает одно: образец, который собирается. Она запустила его в песочнице и сохранила подписанную квитанцию. Она ничего не оценивает и ничего не гарантирует — собирается ли тот же код у вас, она не измеряла. Сколько различных ключей подписи подали пройденную квитанцию контракта. Один — только автор; больше одного — значит, кто-то ещё тоже собрал. Ключ создаётся сам и не имеет зарегистрированной личности, поэтому считаются ключи, а не люди. MIT-0

Свидетельства выполнения

Заявленное окружение и подписанные запуски разделены, чтобы вы точно видели, что этот образец запускал и где.

Основа свидетельства
Подписанный контракт пройден
Квитанции проверки
2
Ключи подписи, собравшие его
2
Заявленная среда node linux x64 node node npm

Среды запусков проверки

Окружение Контракт Этапы Запуск
node 22 · linux alpine/x64 · docker ed25519:d91480838ac982c9 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · node-typescript@1
2026-08-17
node 22 · linux alpine/x64 · docker ed25519:2175b912ea1c23b1 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · node-typescript@1
2026-08-18

Кейс

HOW
Цель
Construct and validate discriminated union schemas in Zod with literal and enum discriminators, optionsMap indexing, and discriminator issue reporting
Пакеты
Символы
  • discriminatedUnion
  • ZodDiscriminatedUnion
Окружение
node
Создан
2026-08-17T13:38:58Z

Контракт

  1. z.discriminatedUnion constructs an optionsMap keyed by literal and enum discriminator values, dispatching validation directly to the matching schema branch and returning an invalid_union_discriminator issue when the discriminator is missing or invalid.
  2. Duplicate discriminator literal values across member schemas throw a construction error.
  3. Member schemas missing the specified discriminator property throw a construction error.
  4. Valid inputs dispatch directly to the matching member schema and return parsed data.
  5. Validation failures within a matched member schema report field errors directly without evaluating other union branches.
  6. Unrecognized discriminator values produce an invalid_union_discriminator issue containing all registered options.
  7. Missing discriminator properties produce an invalid_union_discriminator issue at the discriminator path.
  8. Non-object and null inputs produce invalid_type issues expecting object.
  9. Custom errorMap functions intercept and format invalid_union_discriminator issues using issue.options.

Файлы

  • NOTES.md
  • csx.json
  • package-lock.json
  • package.json
  • src/discriminated_union.mjs
  • test/contract.mjs

Скачать артефакт с исходным кодом (tar.gz)

Исходный код

NOTES.md
# Zod Discriminated Union Construction, Indexing, and Issue Reporting

## Known Solution Search Result
`search_known_solution` returned `MATCH: COMPATIBLE` pointing to existing samples:
- `sha256:f1680e0ed36414762576bfcdd9a2ce0f7d8d735b4f3e2870fa6ab3d2efd3b734` (global and contextual error map configuration and precedence)
- `sha256:30dbed125fd6944057a2d3d053851104df423c6580f301a14ce9d4143dcf07d1` (`.catch()` fallback and preprocess evaluation on absent object keys)
- `sha256:b1df332fb06be1ad1dddffd68c8ee86edefa3d3c5d234d6ebb92ca96fad55572` (basic object validation and error shapes)
- `sha256:d3a9cc5fa6af1de9c4bd13813f2a670ad60d7afe148dadb0bcc4dc95133a9eb4` (constructor coercion traps in `z.coerce` and `.refine()`)

None of these samples cover `z.discriminatedUnion` construction-time verification, `optionsMap` indexing with literal and enum discriminators, single-branch dispatch, or the structure of `invalid_union_discriminator` validation issues.

## Pinned Release
- `zod@3.24.2`

## Observed Behaviour
1. **Discriminator Indexing (`optionsMap`)**:
   `z.discriminatedUnion(discriminator, options)` indexes member schemas into an `optionsMap` (`Map<string, ZodObject>`). When a member schema uses `z.enum(['a', 'b'])` as its discriminator, Zod expands each enum value into separate entries in `optionsMap` pointing to that same schema instance.
2. **Construction-Time Validation**:
   - Defining duplicate discriminator literal values across member schemas throws an `Error` (`Discriminator property ${discriminator} has duplicate value ${val}`).
   - Supplying a member schema that lacks the discriminator property throws an `Error` (`A discriminator value for key \`${discriminator}\` could not be extracted from all schema options`).
3. **Single-Branch Dispatch**:
   When input contains a recognized discriminator value, Zod delegates validation solely to the matching schema in `optionsMap`. If fields within that branch fail validation, Zod returns issues for that branch directly, without testing other union branches or wrapping errors in an `invalid_union` unionErrors array.
4. **Discriminator Issue Structure (`invalid_union_discriminator`)**:
   When input is an object but the discriminator key is missing or set to an unrecognized value, Zod emits a single issue with `code: 'invalid_union_discriminator'`, `path: [discriminator]`, and `options` containing an array of all valid discriminator strings.
5. **Non-Object / Null Inputs**:
   Non-object or `null` inputs fail fast with `code: 'invalid_type'` and `expected: 'object'` at root `path: []`.
6. **Error Map Integration**:
   Custom `errorMap` handlers receive the `invalid_union_discriminator` issue code along with `issue.options`, allowing custom error formatting with all valid discriminator options.
csx.json
{"case":{"caseId":"case:sha256:dd7660812090ecefeb86b1d5fd7151c47832cc618ba3bd1962cd1abcf145a09a","contract":["z.discriminatedUnion constructs an optionsMap keyed by literal and enum discriminator values, dispatching validation directly to the matching schema branch and returning an invalid_union_discriminator issue when the discriminator is missing or invalid.","Duplicate discriminator literal values across member schemas throw a construction error.","Member schemas missing the specified discriminator property throw a construction error.","Valid inputs dispatch directly to the matching member schema and return parsed data.","Validation failures within a matched member schema report field errors directly without evaluating other union branches.","Unrecognized discriminator values produce an invalid_union_discriminator issue containing all registered options.","Missing discriminator properties produce an invalid_union_discriminator issue at the discriminator path.","Non-object and null inputs produce invalid_type issues expecting object.","Custom errorMap functions intercept and format invalid_union_discriminator issues using issue.options."],"goal":"Construct and validate discriminated union schemas in Zod with literal and enum discriminators, optionsMap indexing, and discriminator issue reporting","kind":"HOW","packages":["pkg:npm/zod@3.24.2"],"schemaVersion":1,"symbols":["discriminatedUnion","ZodDiscriminatedUnion"]},"contractCommand":["node","test/contract.mjs"],"environment":{"arch":"x64","ecosystem":"npm","executionContext":"node","language":"node","os":"linux","packageManager":"npm","runtime":"node","schemaVersion":1},"license":"MIT-0","packages":["pkg:npm/zod@3.24.2"],"schemaVersion":1,"symbols":["discriminatedUnion","ZodDiscriminatedUnion"],"verifierAdapter":"node-typescript@1"}
package-lock.json
{
  "name": "npm-zod-discriminated-union",
  "version": "1.0.0",
  "lockfileVersion": 3,
  "requires": true,
  "packages": {
    "": {
      "name": "npm-zod-discriminated-union",
      "version": "1.0.0",
      "license": "MIT-0",
      "dependencies": {
        "zod": "3.24.2"
      }
    },
    "node_modules/zod": {
      "version": "3.24.2",
      "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz",
      "integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==",
      "license": "MIT",
      "funding": {
        "url": "https://github.com/sponsors/colinhacks"
      }
    }
  }
}
package.json
{
  "name": "npm-zod-discriminated-union",
  "version": "1.0.0",
  "private": true,
  "type": "module",
  "license": "MIT-0",
  "dependencies": {
    "zod": "3.24.2"
  }
}
src/discriminated_union.mjs
import { z, discriminatedUnion, ZodDiscriminatedUnion } from 'zod';

/**
 * Creates a discriminated union of geometric shape schemas keyed on the 'kind' property.
 * Includes both literal discriminator branches and a multi-value enum discriminator branch.
 *
 * @returns {ZodDiscriminatedUnion<'kind', [z.ZodObject<any>, z.ZodObject<any>, z.ZodObject<any>]>}
 */
export function createShapeUnion() {
  const circleSchema = z.object({
    kind: z.literal('circle'),
    radius: z.number().positive(),
  });

  const squareSchema = z.object({
    kind: z.literal('square'),
    side: z.number().positive(),
  });

  const rectangleSchema = z.object({
    kind: z.enum(['rect', 'box']),
    width: z.number().positive(),
    height: z.number().positive(),
  });

  return discriminatedUnion('kind', [circleSchema, squareSchema, rectangleSchema]);
}

/**
 * Creates a discriminated union configured with a custom error map.
 * Demonstrates intercepting invalid_union_discriminator issues.
 *
 * @returns {ZodDiscriminatedUnion<'kind', [z.ZodObject<any>, z.ZodObject<any>]>}
 */
export function createShapeUnionWithCustomErrorMap() {
  const circleSchema = z.object({
    kind: z.literal('circle'),
    radius: z.number().positive(),
  });

  const squareSchema = z.object({
    kind: z.literal('square'),
    side: z.number().positive(),
  });

  return discriminatedUnion('kind', [circleSchema, squareSchema], {
    errorMap: (issue, ctx) => {
      if (issue.code === z.ZodIssueCode.invalid_union_discriminator) {
        return {
          message: `Expected kind to be one of: ${issue.options.join(', ')}`,
        };
      }
      return { message: ctx.defaultError };
    },
  });
}

/**
 * Helper that attempts to construct an invalid discriminated union with duplicate discriminator tags.
 */
export function constructDuplicateDiscriminatorUnion() {
  return discriminatedUnion('kind', [
    z.object({ kind: z.literal('circle'), radius: z.number() }),
    z.object({ kind: z.literal('circle'), diameter: z.number() }),
  ]);
}

/**
 * Helper that attempts to construct a discriminated union with a member missing the discriminator key.
 */
export function constructMissingDiscriminatorUnion() {
  return discriminatedUnion('kind', [
    z.object({ tag: z.literal('circle'), radius: z.number() }),
  ]);
}
test/contract.mjs
import assert from 'node:assert/strict';
import { z, ZodDiscriminatedUnion, ZodIssueCode } from 'zod';
import {
  createShapeUnion,
  createShapeUnionWithCustomErrorMap,
  constructDuplicateDiscriminatorUnion,
  constructMissingDiscriminatorUnion,
} from '../src/discriminated_union.mjs';

// ---------------------------------------------------------------------------
// 1. z.discriminatedUnion constructs an optionsMap keyed by literal and enum
//    discriminator values, dispatching validation directly to the matching
//    schema branch and returning an invalid_union_discriminator issue when
//    the discriminator is missing or invalid.
// ---------------------------------------------------------------------------
const shapeUnion = createShapeUnion();

assert.equal(
  shapeUnion instanceof ZodDiscriminatedUnion,
  true,
  'shapeUnion must be an instance of ZodDiscriminatedUnion'
);
assert.equal(
  shapeUnion.discriminator,
  'kind',
  'discriminator property must report the configured discriminator key'
);
assert.equal(
  shapeUnion.options.length,
  3,
  'options array must contain the 3 member object schemas'
);
assert.equal(
  shapeUnion.optionsMap.size,
  4,
  'optionsMap must contain 4 keys expanding the 2 literals and the 2 enum values'
);
assert.equal(shapeUnion.optionsMap.has('circle'), true);
assert.equal(shapeUnion.optionsMap.has('square'), true);
assert.equal(shapeUnion.optionsMap.has('rect'), true);
assert.equal(shapeUnion.optionsMap.has('box'), true);

// ---------------------------------------------------------------------------
// 2. Construction validation: duplicate keys and missing discriminator schemas throw synchronously.
// ---------------------------------------------------------------------------
assert.throws(
  () => constructDuplicateDiscriminatorUnion(),
  {
    name: 'Error',
    message: /Discriminator property kind has duplicate value circle/,
  },
  'duplicate discriminator value across branches must throw at construction time'
);

assert.throws(
  () => constructMissingDiscriminatorUnion(),
  {
    name: 'Error',
    message: /A discriminator value for key `kind` could not be extracted/,
  },
  'schema branch missing discriminator property must throw at construction time'
);

// ---------------------------------------------------------------------------
// 3. Valid input parsing dispatches to the corresponding member schema.
// ---------------------------------------------------------------------------
const circleResult = shapeUnion.safeParse({ kind: 'circle', radius: 10 });
assert.equal(circleResult.success, true);
assert.deepEqual(circleResult.data, { kind: 'circle', radius: 10 });

const boxResult = shapeUnion.safeParse({ kind: 'box', width: 20, height: 30 });
assert.equal(boxResult.success, true);
assert.deepEqual(boxResult.data, { kind: 'box', width: 20, height: 30 });

// ---------------------------------------------------------------------------
// 4. Matched branch failure isolates validation issues to the selected schema branch.
// ---------------------------------------------------------------------------
const branchFailResult = shapeUnion.safeParse({ kind: 'circle', radius: -5 });
assert.equal(branchFailResult.success, false);
assert.equal(branchFailResult.error.issues.length, 1);
assert.equal(
  branchFailResult.error.issues[0].code,
  ZodIssueCode.too_small,
  'branch error code must reflect the failed constraint on the selected schema'
);
assert.deepEqual(
  branchFailResult.error.issues[0].path,
  ['radius'],
  'branch error path must point to the invalid field in the selected schema'
);

// ---------------------------------------------------------------------------
// 5. Unrecognized discriminator value produces an invalid_union_discriminator issue.
// ---------------------------------------------------------------------------
const unrecognizedResult = shapeUnion.safeParse({ kind: 'hexagon', side: 5 });
assert.equal(unrecognizedResult.success, false);
assert.equal(unrecognizedResult.error.issues.length, 1);
assert.equal(
  unrecognizedResult.error.issues[0].code,
  ZodIssueCode.invalid_union_discriminator
);
assert.deepEqual(unrecognizedResult.error.issues[0].path, ['kind']);
assert.deepEqual(
  unrecognizedResult.error.issues[0].options,
  ['circle', 'square', 'rect', 'box'],
  'issue.options must list all registered discriminator values'
);

// ---------------------------------------------------------------------------
// 6. Missing discriminator property produces an invalid_union_discriminator issue.
// ---------------------------------------------------------------------------
const missingDiscriminatorResult = shapeUnion.safeParse({ radius: 10 });
assert.equal(missingDiscriminatorResult.success, false);
assert.equal(missingDiscriminatorResult.error.issues.length, 1);
assert.equal(
  missingDiscriminatorResult.error.issues[0].code,
  ZodIssueCode.invalid_union_discriminator
);
assert.deepEqual(missingDiscriminatorResult.error.issues[0].path, ['kind']);

// ---------------------------------------------------------------------------
// 7. Non-object and null inputs fail with invalid_type expecting object.
// ---------------------------------------------------------------------------
const stringInputResult = shapeUnion.safeParse('not-an-object');
assert.equal(stringInputResult.success, false);
assert.equal(
  stringInputResult.error.issues[0].code,
  ZodIssueCode.invalid_type
);
assert.equal(stringInputResult.error.issues[0].expected, 'object');
assert.equal(stringInputResult.error.issues[0].received, 'string');
assert.deepEqual(stringInputResult.error.issues[0].path, []);

const nullInputResult = shapeUnion.safeParse(null);
assert.equal(nullInputResult.success, false);
assert.equal(
  nullInputResult.error.issues[0].code,
  ZodIssueCode.invalid_type
);
assert.equal(nullInputResult.error.issues[0].expected, 'object');
assert.equal(nullInputResult.error.issues[0].received, 'null');

// ---------------------------------------------------------------------------
// 8. Custom errorMap intercepts invalid_union_discriminator issues.
// ---------------------------------------------------------------------------
const customErrorUnion = createShapeUnionWithCustomErrorMap();
const customResult = customErrorUnion.safeParse({ kind: 'triangle' });
assert.equal(customResult.success, false);
assert.equal(
  customResult.error.issues[0].message,
  'Expected kind to be one of: circle, square',
  'custom errorMap must receive and format invalid_union_discriminator issue'
);

console.log('CONTRACT PASS: zod discriminated union behaviour verified');

Исходный сидер

csx-seed