Exemple
zod 3.24.2: Construct and validate discriminated union schemas in Zod with literal and enum discriminators, optionsMap indexing, and discriminator issue reporting
Échantillon vérifié pour npm zod 3.24.2: Construct and validate discriminated union schemas in Zod with literal and enum discriminators, optionsMap indexing…
sha256:32f249dd8e24038855d56253549bcee08afe8de71af21aab61c0b489257727db
Ce réseau offre une seule chose : un échantillon qui compile. Il l'a exécuté dans un bac à sable et conservé le reçu signé. Il ne note rien et ne garantit rien : si le même code compile chez vous, il ne l'a pas mesuré.
Combien de clés de signature distinctes ont déposé un reçu de contrat réussi. Une seule, c'est l'auteur ; plus d'une signifie que quelqu'un d'autre l'a compilé aussi. Une clé est auto-générée sans identité enregistrée derrière, donc on compte des clés, pas des personnes.
MIT-0
Preuves d'exécution
L'environnement déclaré et les exécutions signées sont séparés, pour que vous voyiez exactement ce que cet échantillon a exécuté et où.
- Base de preuve
- Contrat signé réussi
- Reçus de vérification
- 2
- Clés de signature qui l’ont compilé
- 2
Environnement déclaré
node linux x64 node node npm
Environnements des exécutions de vérification
| Environnement | Contrat | Étapes | Exécution |
|---|---|---|---|
| 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 |
Cas
HOW- Objectif
- Construct and validate discriminated union schemas in Zod with literal and enum discriminators, optionsMap indexing, and discriminator issue reporting
- Paquets
- Symboles
-
- discriminatedUnion
- ZodDiscriminatedUnion
- Environnement
- node
- Créé
- 2026-08-17T13:38:58Z
Contrat
- 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.
Fichiers
- NOTES.md
- csx.json
- package-lock.json
- package.json
- src/discriminated_union.mjs
- test/contract.mjs
Code source
# 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.
{"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"}
{
"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"
}
}
}
}
{
"name": "npm-zod-discriminated-union",
"version": "1.0.0",
"private": true,
"type": "module",
"license": "MIT-0",
"dependencies": {
"zod": "3.24.2"
}
}
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() }),
]);
}
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');