union.js 3.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980
  1. import { parseDef } from "../parseDef.js";
  2. export const primitiveMappings = {
  3. ZodString: "string",
  4. ZodNumber: "number",
  5. ZodBigInt: "integer",
  6. ZodBoolean: "boolean",
  7. ZodNull: "null",
  8. };
  9. export function parseUnionDef(def, refs) {
  10. if (refs.target === "openApi3")
  11. return asAnyOf(def, refs);
  12. const options = def.options instanceof Map ? Array.from(def.options.values()) : def.options;
  13. // This blocks tries to look ahead a bit to produce nicer looking schemas with type array instead of anyOf.
  14. if (options.every((x) => x._def.typeName in primitiveMappings &&
  15. (!x._def.checks || !x._def.checks.length))) {
  16. // all types in union are primitive and lack checks, so might as well squash into {type: [...]}
  17. const types = options.reduce((types, x) => {
  18. const type = primitiveMappings[x._def.typeName]; //Can be safely casted due to row 43
  19. return type && !types.includes(type) ? [...types, type] : types;
  20. }, []);
  21. return {
  22. type: types.length > 1 ? types : types[0],
  23. };
  24. }
  25. else if (options.every((x) => x._def.typeName === "ZodLiteral" && !x.description)) {
  26. // all options literals
  27. const types = options.reduce((acc, x) => {
  28. const type = typeof x._def.value;
  29. switch (type) {
  30. case "string":
  31. case "number":
  32. case "boolean":
  33. return [...acc, type];
  34. case "bigint":
  35. return [...acc, "integer"];
  36. case "object":
  37. if (x._def.value === null)
  38. return [...acc, "null"];
  39. case "symbol":
  40. case "undefined":
  41. case "function":
  42. default:
  43. return acc;
  44. }
  45. }, []);
  46. if (types.length === options.length) {
  47. // all the literals are primitive, as far as null can be considered primitive
  48. const uniqueTypes = types.filter((x, i, a) => a.indexOf(x) === i);
  49. return {
  50. type: uniqueTypes.length > 1 ? uniqueTypes : uniqueTypes[0],
  51. enum: options.reduce((acc, x) => {
  52. return acc.includes(x._def.value) ? acc : [...acc, x._def.value];
  53. }, []),
  54. };
  55. }
  56. }
  57. else if (options.every((x) => x._def.typeName === "ZodEnum")) {
  58. return {
  59. type: "string",
  60. enum: options.reduce((acc, x) => [
  61. ...acc,
  62. ...x._def.values.filter((x) => !acc.includes(x)),
  63. ], []),
  64. };
  65. }
  66. return asAnyOf(def, refs);
  67. }
  68. const asAnyOf = (def, refs) => {
  69. const anyOf = (def.options instanceof Map
  70. ? Array.from(def.options.values())
  71. : def.options)
  72. .map((x, i) => parseDef(x._def, {
  73. ...refs,
  74. currentPath: [...refs.currentPath, "anyOf", `${i}`],
  75. }))
  76. .filter((x) => !!x &&
  77. (!refs.strictUnions ||
  78. (typeof x === "object" && Object.keys(x).length > 0)));
  79. return anyOf.length ? { anyOf } : undefined;
  80. };