Skip to content

Conversation

@sentik
Copy link
Contributor

@sentik sentik commented Oct 29, 2025

Add Support for Circular References in Zod Schemas

Problem

When OpenAPI schemas contain circular or self-referencing structures (e.g., a Node schema that references itself via a child property), Orval was generating incorrect Zod validation schemas. Instead of using zod.lazy() to handle the circular reference properly, it would generate zod.unknown() or zod.any(), making the validation schemas non-functional.

Example Issue:

components:
  schemas:
    Node:
      type: object
      properties:
        id:
          type: integer
        child:
          $ref: '#/components/schemas/Node'  # Circular reference

Before (Incorrect):

export const Node = zod.object({
  "id": zod.number(),
  "child": zod.unknown()  // ❌ Wrong!
})

After (Correct):

export const Node = zod.object({
  "id": zod.number(),
  "child": zod.lazy(() => Node).optional()  // ✅ Correct!
})

// Additionally, the schema is now exported for reuse

Solution

Implemented comprehensive support for circular references by:

  1. Detection: Added __circular__ and __refName__ markers to identify circular references during schema dereferencing in the deference function.

  2. Processing: Modified generateZodValidationSchemaDefinition to handle circular reference markers and generate ['circularRef', refName] function definitions.

  3. Code Generation: Updated parseZodValidationSchemaDefinition to:

    • Extract schema names from references (e.g., #/components/schemas/NodeNode)
    • Wrap circular references in zod.lazy(() => SchemaName)
    • Properly apply modifiers like .optional() and .nullable() after the lazy wrapper
  4. Array Support: Extended the array parsing logic to handle circular references within arrays, generating zod.array(zod.lazy(() => SchemaName)).

  5. Schema Export: Implemented automatic detection and export of schemas referenced in circular dependencies, ensuring the referenced schemas are available in the generated code.

  6. Self-Reference Handling: Enhanced the deference function to detect self-references within a schema being generated, ensuring proper circular reference handling even when a schema references itself.

Changes

  • packages/zod/src/index.ts:

    • Modified deference() to return circular reference markers
    • Updated generateZodValidationSchemaDefinition() to detect and handle circular references
    • Enhanced parseZodValidationSchemaDefinition() to generate zod.lazy() wrappers
    • Added circular reference detection for self-referencing schemas
    • Implemented automatic schema generation for circular dependencies
    • Extended array handling to support circular references
  • packages/zod/src/zod.test.ts:

    • Added comprehensive unit tests for circular references covering:
      • Simple circular references
      • Circular references in arrays
      • Multiple circular references in one object
      • Combination with optional/nullable modifiers
      • Deeply nested structures
      • AllOf combinations
      • Description annotations
      • Array modifiers

Testing

Verified with real-world scenarios:

  • Simple self-referencing schemas
  • Complex multi-level circular dependencies
  • Circular references in arrays
  • Schema inheritance patterns

Examples

Simple Circular Reference:

User:
  properties:
    manager:
      $ref: '#/components/schemas/User'

Generates: manager: zod.lazy(() => User).optional()

Circular Reference in Array:

File:
  properties:
    children:
      type: array
      items:
        $ref: '#/components/schemas/File'

Generates: children: zod.array(zod.lazy(() => File)).optional()

Complex Scenario (User Hierarchy):

User:
  properties:
    manager: $ref: '#/components/schemas/User'
    directReports:
      type: array
      items: $ref: '#/components/schemas/User'

Generates both the endpoint schemas and exports the User schema definition.

Closes

Closes #2332

Fixed a bug where falsy default values (false, 0, '', null) were not properly
applied to generated Zod schemas. The condition used `schema.default` which
falsely treats falsy values as undefined.

Changed the check to `schema.default !== undefined` to properly detect when
a default value is set, regardless of its truthiness.

Added test coverage for:
- default: true
- default: false
- no default (undefined)

Fixes orval-labs#2222
@melloware melloware added the zod Zod related issue label Oct 29, 2025
@melloware
Copy link
Collaborator

Looks like some merge conflicts

Copy link
Collaborator

@melloware melloware left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please fix merge conflicts

@sentik sentik marked this pull request as draft October 30, 2025 00:42
@melloware
Copy link
Collaborator

@sentik can you rebase from main and fix the merge conflicts?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

zod Zod related issue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add support for recursion/circular references in Zod

2 participants