zod 4.2.0-canary.20251118T055019 → 4.2.0-canary.20251118T055142
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -706,6 +706,54 @@ describe("toJSONSchema", () => {
|
|
|
706
706
|
`);
|
|
707
707
|
});
|
|
708
708
|
|
|
709
|
+
test("discriminated unions", () => {
|
|
710
|
+
const schema = z.discriminatedUnion("type", [
|
|
711
|
+
z.object({ type: z.literal("success"), data: z.string() }),
|
|
712
|
+
z.object({ type: z.literal("error"), message: z.string() }),
|
|
713
|
+
]);
|
|
714
|
+
expect(z.toJSONSchema(schema)).toMatchInlineSnapshot(`
|
|
715
|
+
{
|
|
716
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
717
|
+
"oneOf": [
|
|
718
|
+
{
|
|
719
|
+
"additionalProperties": false,
|
|
720
|
+
"properties": {
|
|
721
|
+
"data": {
|
|
722
|
+
"type": "string",
|
|
723
|
+
},
|
|
724
|
+
"type": {
|
|
725
|
+
"const": "success",
|
|
726
|
+
"type": "string",
|
|
727
|
+
},
|
|
728
|
+
},
|
|
729
|
+
"required": [
|
|
730
|
+
"type",
|
|
731
|
+
"data",
|
|
732
|
+
],
|
|
733
|
+
"type": "object",
|
|
734
|
+
},
|
|
735
|
+
{
|
|
736
|
+
"additionalProperties": false,
|
|
737
|
+
"properties": {
|
|
738
|
+
"message": {
|
|
739
|
+
"type": "string",
|
|
740
|
+
},
|
|
741
|
+
"type": {
|
|
742
|
+
"const": "error",
|
|
743
|
+
"type": "string",
|
|
744
|
+
},
|
|
745
|
+
},
|
|
746
|
+
"required": [
|
|
747
|
+
"type",
|
|
748
|
+
"message",
|
|
749
|
+
],
|
|
750
|
+
"type": "object",
|
|
751
|
+
},
|
|
752
|
+
],
|
|
753
|
+
}
|
|
754
|
+
`);
|
|
755
|
+
});
|
|
756
|
+
|
|
709
757
|
test("intersections", () => {
|
|
710
758
|
const schema = z.intersection(z.object({ name: z.string() }), z.object({ age: z.number() }));
|
|
711
759
|
|
|
@@ -330,13 +330,20 @@ export class JSONSchemaGenerator {
|
|
|
330
330
|
}
|
|
331
331
|
case "union": {
|
|
332
332
|
const json: JSONSchema.BaseSchema = _json as any;
|
|
333
|
+
// Discriminated unions use oneOf (exactly one match) instead of anyOf (one or more matches)
|
|
334
|
+
// because the discriminator field ensures mutual exclusivity between options in JSON Schema
|
|
335
|
+
const isDiscriminated = (def as any).discriminator !== undefined;
|
|
333
336
|
const options = def.options.map((x, i) =>
|
|
334
337
|
this.process(x, {
|
|
335
338
|
...params,
|
|
336
|
-
path: [...params.path, "anyOf", i],
|
|
339
|
+
path: [...params.path, isDiscriminated ? "oneOf" : "anyOf", i],
|
|
337
340
|
})
|
|
338
341
|
);
|
|
339
|
-
|
|
342
|
+
if (isDiscriminated) {
|
|
343
|
+
json.oneOf = options;
|
|
344
|
+
} else {
|
|
345
|
+
json.anyOf = options;
|
|
346
|
+
}
|
|
340
347
|
break;
|
|
341
348
|
}
|
|
342
349
|
case "intersection": {
|
|
@@ -252,11 +252,19 @@ class JSONSchemaGenerator {
|
|
|
252
252
|
}
|
|
253
253
|
case "union": {
|
|
254
254
|
const json = _json;
|
|
255
|
+
// Discriminated unions use oneOf (exactly one match) instead of anyOf (one or more matches)
|
|
256
|
+
// because the discriminator field ensures mutual exclusivity between options in JSON Schema
|
|
257
|
+
const isDiscriminated = def.discriminator !== undefined;
|
|
255
258
|
const options = def.options.map((x, i) => this.process(x, {
|
|
256
259
|
...params,
|
|
257
|
-
path: [...params.path, "anyOf", i],
|
|
260
|
+
path: [...params.path, isDiscriminated ? "oneOf" : "anyOf", i],
|
|
258
261
|
}));
|
|
259
|
-
|
|
262
|
+
if (isDiscriminated) {
|
|
263
|
+
json.oneOf = options;
|
|
264
|
+
}
|
|
265
|
+
else {
|
|
266
|
+
json.anyOf = options;
|
|
267
|
+
}
|
|
260
268
|
break;
|
|
261
269
|
}
|
|
262
270
|
case "intersection": {
|
|
@@ -248,11 +248,19 @@ export class JSONSchemaGenerator {
|
|
|
248
248
|
}
|
|
249
249
|
case "union": {
|
|
250
250
|
const json = _json;
|
|
251
|
+
// Discriminated unions use oneOf (exactly one match) instead of anyOf (one or more matches)
|
|
252
|
+
// because the discriminator field ensures mutual exclusivity between options in JSON Schema
|
|
253
|
+
const isDiscriminated = def.discriminator !== undefined;
|
|
251
254
|
const options = def.options.map((x, i) => this.process(x, {
|
|
252
255
|
...params,
|
|
253
|
-
path: [...params.path, "anyOf", i],
|
|
256
|
+
path: [...params.path, isDiscriminated ? "oneOf" : "anyOf", i],
|
|
254
257
|
}));
|
|
255
|
-
|
|
258
|
+
if (isDiscriminated) {
|
|
259
|
+
json.oneOf = options;
|
|
260
|
+
}
|
|
261
|
+
else {
|
|
262
|
+
json.anyOf = options;
|
|
263
|
+
}
|
|
256
264
|
break;
|
|
257
265
|
}
|
|
258
266
|
case "intersection": {
|