{"record":{"id":"1dfe069b2436a50c","repo":"colinhacks/zod","slug":"discriminator-property-string-discriminator-ha","errorCode":null,"errorMessage":"Discriminator property ${String(discriminator)} has duplicate value ${String(value)}","messagePattern":"Discriminator property (.+?) has duplicate value (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/zod/src/v3/types.ts","lineNumber":3206,"sourceCode":"  >(\n    discriminator: Discriminator,\n    options: Types,\n    params?: RawCreateParams\n  ): ZodDiscriminatedUnion<Discriminator, Types> {\n    // Get all the valid discriminator values\n    const optionsMap: Map<Primitive, Types[number]> = new Map();\n\n    // try {\n    for (const type of options) {\n      const discriminatorValues = getDiscriminator(type.shape[discriminator]);\n      if (!discriminatorValues.length) {\n        throw new Error(\n          `A discriminator value for key \\`${discriminator}\\` could not be extracted from all schema options`\n        );\n      }\n      for (const value of discriminatorValues) {\n        if (optionsMap.has(value)) {\n          throw new Error(`Discriminator property ${String(discriminator)} has duplicate value ${String(value)}`);\n        }\n\n        optionsMap.set(value, type);\n      }\n    }\n\n    return new ZodDiscriminatedUnion<\n      Discriminator,\n      // DiscriminatorValue,\n      Types\n    >({\n      typeName: ZodFirstPartyTypeKind.ZodDiscriminatedUnion,\n      discriminator,\n      options,\n      optionsMap,\n      ...processCreateParams(params),\n    });\n  }","sourceCodeStart":3188,"sourceCodeEnd":3224,"githubUrl":"https://github.com/colinhacks/zod/blob/2d90846af918af9602e088812d63a035d47cdbe4/packages/zod/src/v3/types.ts#L3188-L3224","documentation":"Thrown at schema-construction time by z.discriminatedUnion (packages/zod/src/v3/types.ts:3206) when two option schemas produce the same discriminator value. The discriminated union dispatches on the discriminator, so each literal value must map to exactly one option; a collision makes routing ambiguous and Zod refuses to build the optionsMap.","triggerScenarios":"Calling `z.discriminatedUnion('kind', [A, B])` where both A and B expose `kind: z.literal('same')`, or where an option uses z.enum(['x','y']) and another uses z.literal('x'), producing overlapping values that the getDiscriminator walk collects into the same map key.","commonSituations":"Copy-pasting a branch and forgetting to change its discriminator literal; merging two schemas that share a tag value; refactoring an enum-valued discriminator where one literal now overlaps another branch; using z.union of literals that expands to values already used elsewhere.","solutions":["Give each option a unique discriminator literal value — the duplicate value is named verbatim in the error message.","If two branches legitimately share a tag, merge them into a single option using z.union on the differing fields, or switch to z.union and disambiguate via refinement.","Audit z.enum and multi-literal discriminators: each literal across all options must appear in exactly one option.","Rebuild the union incrementally (add one option at a time) so the colliding pair is obvious."],"exampleFix":"// before\nconst A = z.object({ type: z.literal('circle'), r: z.number() });\nconst B = z.object({ type: z.literal('circle'), side: z.number() }); // duplicate 'circle'\nconst Shape = z.discriminatedUnion('type', [A, B]);\n\n// after\nconst B = z.object({ type: z.literal('square'), side: z.number() });\nconst Shape = z.discriminatedUnion('type', [A, B]);","handlingStrategy":"validation","validationCode":"import { z, ZodType } from 'zod';\n\nfunction assertUniqueDiscriminatorValues(discriminator: string, options: ZodType[]) {\n  const seen = new Map<unknown, number>();\n  for (const o of options) {\n    const def = (o as any).shape?.[discriminator]?._def;\n    const values = def?.values ?? (def?.value !== undefined ? [def.value] : []);\n    for (const v of values) seen.set(v, (seen.get(v) ?? 0) + 1);\n  }\n  const dupes = [...seen.entries()].filter(([, n]) => n > 1);\n  if (dupes.length) throw new Error(`Duplicate discriminator values: ${dupes.map(([k]) => JSON.stringify(k)).join(', ')}`);\n}","typeGuard":"null","tryCatchPattern":"null","preventionTips":["Assign each branch a distinct literal value for the discriminator.","When sharing a base object, override the discriminator literal in each branch.","If using z.enum as the discriminator, ensure the enum sets across branches are disjoint.","Add a unit test that constructs the union so duplicate-value errors surface at test time, not runtime."],"tags":["discriminated-union","schema-construction","duplicate","discriminator"],"backgroundTag":null,"analyzedSha":"2d90846af918af9602e088812d63a035d47cdbe4","analyzedAt":"2026-08-11T01:21:44.015Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-22T11:17:16.035Z"}