zod icon indicating copy to clipboard operation
zod copied to clipboard

Override isn't called in all instances of the schema

Open reteps opened this issue 4 months ago • 9 comments

If a schema is referenced multiple times, and the second usage of the schema isn't the same, the override is not processed.

const fooSchema = z.string().meta({
  id: 'ORIGINAL',
});

const mySchemaCorrect = z.object({
  foo: fooSchema,
  foo2: fooSchema,
});

const mySchemaIncorrect = z.object({
  foo: fooSchema.describe('A foo string'),
  foo2: fooSchema,
});

const parseSchema = (schema: ZodType<any>) => {
  return z.toJSONSchema(schema, {
    target: 'draft-7',
    unrepresentable: 'throw',
    cycles: 'throw',
    reused: 'inline',
    io: 'input',
    override(ctx) {
      if ('id' in ctx.jsonSchema) {
        ctx.jsonSchema.id = 'CHANGED';
      }
    },
  });
};

const jsonSchemaCorrect = parseSchema(mySchemaCorrect);
/*
{
  '$schema': 'http://json-schema.org/draft-07/schema#',
  type: 'object',
  properties: {
    foo: { '$ref': '#/definitions/ORIGINAL' },
    foo2: { '$ref': '#/definitions/ORIGINAL' }
  },
  required: [ 'foo', 'foo2' ],
  definitions: { ORIGINAL: { id: 'CHANGED', type: 'string' } }
}
*/
const jsonSchemaIncorrect = parseSchema(mySchemaIncorrect);
/*
{
  '$schema': 'http://json-schema.org/draft-07/schema#',
  type: 'object',
  properties: {
    foo: {
      description: 'A foo string',
      allOf: [ { '$ref': '#/definitions/ORIGINAL' } ]
    },
    foo2: { '$ref': '#/definitions/ORIGINAL' }
  },
  required: [ 'foo', 'foo2' ],
  definitions: { ORIGINAL: { id: 'ORIGINAL', type: 'string' } }
}
*/

This prevents me from commenting usages of the schema. I needed this to delete the id property from the JSON Schema, using:

if ('id' in ctx.jsonSchema) {
    ctx.jsonSchema.id = undefined;
}

reteps avatar Aug 15 '25 21:08 reteps

Related #5078

reteps avatar Aug 15 '25 21:08 reteps

I'm a bot trained on Zod's codebase and issues.

Issue #5078 wasn't found, but I did review related threads and the codebase. The behavior you're seeing is intentional in Zod v4: when a schema is reused and then modified (like with .describe()), Zod treats the modified instance as a distinct schema. This means it gets inlined or wrapped (e.g., with allOf), and the override callback is only called for the $ref/definition, not for the inlined or extended usage. This design avoids conflicting overrides when schemas are customized per usage, but it does prevent commenting or overriding metadata for those inlined instances. See this explanation and the docs.

If you want the override to apply everywhere, you could avoid modifying the schema per usage, or use the override to remove the id property so Zod inlines the schema. If you need to access metadata in the override callback, you may need a type assertion due to TypeScript types (example).

Let me know your Zod version if you need more specific advice. If this answers your question, feel free to close the issue!

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Chat with Dosu Join Discord Share on X

dosubot[bot] avatar Aug 15 '25 21:08 dosubot[bot]

This also doesn't work if I remove it by setting it to undefined @dosu.

reteps avatar Aug 15 '25 21:08 reteps

Additionally, if it was called for the inlined usage it would still show "changed".

reteps avatar Aug 15 '25 21:08 reteps

Setting ctx.jsonSchema.id = undefined doesn't actually remove the property from the output—it just sets its value to undefined, so it still appears in the JSON Schema. To remove the id property, use the delete operator: delete ctx.jsonSchema.id. This is supported and will remove the property from the final schema output in standard schema conversion (source).

Example:

override(ctx) {
  if ('id' in ctx.jsonSchema) {
    delete ctx.jsonSchema.id;
  }
}

If you're using a registry-based approach with toJSONSchema, note that definitions extraction and $ref assignments rely on metadata in the registry, not just the JSON Schema object—so deleting id in the override won't prevent $defs extraction in that case.

Let me know if using delete works for your use case! If not, I can help investigate further.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Chat with Dosu Join Discord Share on X

dosubot[bot] avatar Aug 15 '25 21:08 dosubot[bot]

That is incorrect. delete still shows the id in the second scenario @dosu.

{
  '$schema': 'http://json-schema.org/draft-07/schema#',
  type: 'object',
  properties: {
    foo: { '$ref': '#/definitions/ORIGINAL' },
    foo2: { '$ref': '#/definitions/ORIGINAL' }
  },
  required: [ 'foo', 'foo2' ],
  definitions: { ORIGINAL: { type: 'string' } }
}
{
  '$schema': 'http://json-schema.org/draft-07/schema#',
  type: 'object',
  properties: {
    foo: {
      description: 'A foo string',
      allOf: [ { '$ref': '#/definitions/ORIGINAL' } ]
    },
    foo2: { '$ref': '#/definitions/ORIGINAL' }
  },
  required: [ 'foo', 'foo2' ],
  definitions: { ORIGINAL: { id: 'ORIGINAL', type: 'string' } }

reteps avatar Aug 15 '25 21:08 reteps

It looks like an override is not called here: https://github.com/colinhacks/zod/blob/0313553db1bca4a023ff4767503befc025689188/packages/zod/src/v4/core/to-json-schema.ts#L686

reteps avatar Aug 17 '25 23:08 reteps

Hi, @reteps. I'm Dosu, and I'm helping the Zod team manage their backlog and am marking this issue as stale.

Issue Summary:

  • You reported that the override function in z.toJSONSchema is not called for all instances of a referenced schema when the schema is modified (e.g., with .describe()), leading to inconsistent JSON Schema outputs.
  • This results in unwanted id properties appearing in inlined schemas, which you tried to remove by setting undefined or deleting, but those attempts did not work.
  • The behavior is intentional in Zod v4, where modified schemas are treated as distinct and inlined rather than referenced, so overrides only apply to the original $ref definitions.
  • You pointed out that the override is not invoked for these inlined schemas, referencing the relevant code line.
  • This creates challenges in applying consistent overrides across all schema usages when schemas are extended or altered.

Next Steps:

  • Please let me know if this issue is still relevant with the latest version of Zod, and if so, you can keep the discussion open by commenting here.
  • Otherwise, this issue will be automatically closed in 7 days.

Thanks for your understanding and contribution!

dosubot[bot] avatar Nov 16 '25 16:11 dosubot[bot]

still relevant

reteps avatar Nov 16 '25 18:11 reteps