@gabrielbryk/json-schema-to-zod 2.15.0 → 2.16.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,45 @@
1
1
  # @gabrielbryk/json-schema-to-zod
2
2
 
3
+ ## 2.16.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 21d2701: Add `optionalStrategy` option to control whether optional object properties emit `.optional()` or `.exactOptional()`. Defaults to `"exactOptional"` (current behaviour) for full backward compatibility. Use `"optional"` when callers may produce `{ field: undefined }` via optional chaining or object spreads.
8
+
9
+ ## 2.15.2
10
+
11
+ ### Patch Changes
12
+
13
+ - 21b550f: Fix recursive oneOf with direct self-references using z.union instead of z.xor
14
+
15
+ When a recursive schema has a oneOf containing a direct self-reference (without z.lazy() wrapper),
16
+ the v2.15.0 fix to use z.union for recursive catchall cases didn't apply because the condition
17
+ `isRecursive && (inCatchall || hasLazyMembers)` didn't cover direct self-references.
18
+
19
+ This patch adds detection for direct self-references in oneOf options, ensuring z.union is used
20
+ instead of z.xor for these patterns. z.xor validation fails during parsing when evaluating
21
+ self-referential branches, so z.union is the correct choice.
22
+
23
+ This fixes validation failures for schemas like EventConsumptionStrategy from the Serverless
24
+ Workflow spec which uses: `oneOf: [simple-cases, allOf: [$ref: self, additional-properties]]`
25
+
26
+ ## 2.15.1
27
+
28
+ ### Patch Changes
29
+
30
+ - Fix recursive oneOf with direct self-references using z.union instead of z.xor
31
+
32
+ When a recursive schema has a oneOf containing a direct self-reference (without z.lazy() wrapper),
33
+ the v2.15.0 fix to use z.union for recursive catchall cases didn't apply because the condition
34
+ `isRecursive && (inCatchall || hasLazyMembers)` didn't cover direct self-references.
35
+
36
+ This patch adds detection for direct self-references in oneOf options, ensuring z.union is used
37
+ instead of z.xor for these patterns. z.xor validation fails during parsing when evaluating
38
+ self-referential branches, so z.union is the correct choice.
39
+
40
+ This fixes validation failures for schemas like EventConsumptionStrategy from the Serverless
41
+ Workflow spec which uses: `oneOf: [simple-cases, allOf: [$ref: self, additional-properties]]`
42
+
3
43
  ## 2.15.0
4
44
 
5
45
  ### Minor Changes
@@ -1,6 +1,6 @@
1
1
  import { parseSchema } from "./parseSchema.js";
2
2
  import { half } from "../utils/half.js";
3
- import { shouldUseGetter, zodExactOptional, zodIntersection, zodLooseObject, zodNever, } from "../utils/schemaRepresentation.js";
3
+ import { shouldUseGetter, zodExactOptional, zodIntersection, zodLooseObject, zodNever, zodOptional, } from "../utils/schemaRepresentation.js";
4
4
  const originalIndexKey = "__originalIndex";
5
5
  /**
6
6
  * Check if a schema defines object properties (inline object shape) without any refs.
@@ -34,7 +34,11 @@ const parseObjectShape = (schema, refs, pathPrefix) => {
34
34
  ? schema.required.includes(key)
35
35
  : typeof propSchema === "object" && propSchema.required === true;
36
36
  const optional = !hasDefault && !required;
37
- const valueRep = optional ? zodExactOptional(parsedProp) : parsedProp;
37
+ const valueRep = optional
38
+ ? refs.optionalStrategy === "optional"
39
+ ? zodOptional(parsedProp)
40
+ : zodExactOptional(parsedProp)
41
+ : parsedProp;
38
42
  const isGetter = shouldUseGetter(valueRep, refs.currentSchemaName, refs.cycleRefNames, refs.cycleComponentByName);
39
43
  shapeEntries.push({ key, rep: valueRep, isGetter });
40
44
  }
@@ -5,7 +5,7 @@ import { expandJsdocs } from "../utils/jsdocs.js";
5
5
  import { anyOrUnknown } from "../utils/anyOrUnknown.js";
6
6
  import { buildIntersectionTree } from "../utils/buildIntersectionTree.js";
7
7
  import { collectSchemaProperties } from "../utils/collectSchemaProperties.js";
8
- import { shouldUseGetter, zodCatchall, zodChain, zodExactOptional, zodLooseObject, zodLooseRecord, zodStrictObject, zodString, zodSuperRefine, } from "../utils/schemaRepresentation.js";
8
+ import { shouldUseGetter, zodCatchall, zodChain, zodExactOptional, zodLooseObject, zodLooseRecord, zodOptional, zodStrictObject, zodString, zodSuperRefine, } from "../utils/schemaRepresentation.js";
9
9
  export function parseObject(objectSchema, refs) {
10
10
  const collectedProperties = objectSchema.allOf
11
11
  ? collectSchemaProperties(objectSchema, refs)
@@ -77,7 +77,11 @@ export function parseObject(objectSchema, refs) {
77
77
  ? true
78
78
  : typeof propSchema === "object" && propSchema.required === true;
79
79
  const isOptional = !hasDefault && !isRequired;
80
- const valueRep = isOptional ? zodExactOptional(parsedProp) : parsedProp;
80
+ const valueRep = isOptional
81
+ ? refs.optionalStrategy === "optional"
82
+ ? zodOptional(parsedProp)
83
+ : zodExactOptional(parsedProp)
84
+ : parsedProp;
81
85
  const jsdoc = refs.withJsdocs &&
82
86
  typeof propSchema === "object" &&
83
87
  typeof propSchema.description === "string"
@@ -3,7 +3,7 @@ import { anyOrUnknown } from "../utils/anyOrUnknown.js";
3
3
  import { resolveRef } from "../utils/resolveRef.js";
4
4
  import { collectSchemaProperties } from "../utils/collectSchemaProperties.js";
5
5
  import { wrapRecursiveUnion } from "../utils/wrapRecursiveUnion.js";
6
- import { zodAny, zodDiscriminatedUnion, zodSuperRefine, zodUnion, zodXor, } from "../utils/schemaRepresentation.js";
6
+ import { collectRefNames, zodAny, zodDiscriminatedUnion, zodSuperRefine, zodUnion, zodXor, } from "../utils/schemaRepresentation.js";
7
7
  /**
8
8
  * Check if a schema is a "required-only" validation constraint.
9
9
  * These are schemas that only specify `required` without defining types.
@@ -302,5 +302,11 @@ const shouldUseUnionForRecursiveOneOf = (refs, parsedSchemas) => {
302
302
  const isRecursive = current ? (refs.cycleRefNames?.has(current) ?? false) : false;
303
303
  const inCatchall = current ? (refs.catchallRefNames?.has(current) ?? false) : false;
304
304
  const hasLazyMembers = parsedSchemas.some((rep) => rep.node?.kind === "lazy");
305
- return Boolean(isRecursive && (inCatchall || hasLazyMembers));
305
+ // Check if any option contains a direct self-reference (references the current schema).
306
+ // This handles cases where recursive schemas use direct references instead of z.lazy().
307
+ // Without z.lazy(), z.xor() validation fails on these self-referential branches.
308
+ const hasSelfReference = current
309
+ ? parsedSchemas.some((rep) => rep.node && collectRefNames(rep.node).has(current))
310
+ : false;
311
+ return Boolean(isRecursive && (inCatchall || hasLazyMembers || hasSelfReference));
306
312
  };
@@ -290,6 +290,21 @@ export type Options = {
290
290
  * or schema titles.
291
291
  */
292
292
  oneOfOverrides?: Record<string, "union" | "xor">;
293
+ /**
294
+ * Controls which Zod modifier is used for optional object properties
295
+ * (those absent from `required` and without a `default`).
296
+ *
297
+ * - `"exactOptional"` (default): emits `.exactOptional()`.
298
+ * The key must be entirely absent at runtime; a present `undefined` fails.
299
+ * - `"optional"`: emits `.optional()`.
300
+ * The key may be present with `undefined` or absent entirely.
301
+ *
302
+ * Use `"optional"` when callers may include keys with `undefined` values,
303
+ * which is common when spreading objects or using optional chaining.
304
+ *
305
+ * @default "exactOptional"
306
+ */
307
+ optionalStrategy?: "exactOptional" | "optional";
293
308
  /**
294
309
  * Wrap recursive union schemas in z.lazy() to improve TypeScript inference.
295
310
  * This is useful for mutually recursive discriminated unions with optional properties.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gabrielbryk/json-schema-to-zod",
3
- "version": "2.15.0",
3
+ "version": "2.16.0",
4
4
  "description": "Converts JSON schema objects or files into Zod schemas",
5
5
  "type": "module",
6
6
  "types": "./dist/types/index.d.ts",