Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for "custom" definitions #27

Closed
bacebu4 opened this issue Nov 28, 2022 · 4 comments
Closed

Add support for "custom" definitions #27

bacebu4 opened this issue Nov 28, 2022 · 4 comments

Comments

@bacebu4
Copy link

bacebu4 commented Nov 28, 2022

Motivation

Having big and really deep nested schema would produce some crazy JSON output due to current ("seen" / "not seen") ref strategy.

Solution

What if we could help the strategy and mark a ZodObject with .brand() method to make it as separate definition?

const addressSchema = z.object({
      street: z.string(),
      number: z.number(),
      city: z.string(),
    }).brand<'Address'>();
    const someAddresses = z.object({
      address1: addressSchema,
      address2: addressSchema,
      lotsOfAddresses: z.array(addressSchema),
    });
    const jsonSchema = {
      $schema: "http://json-schema.org/draft-07/schema#",
      type: "object",
      definitions: {
        Address: {
          type: "object",
          properties: {
            street: { type: "string" },
            number: { type: "number" },
            city: { type: "string" },
          },
          additionalProperties: false,
          required: ["street", "number", "city"],
        }
      }
      properties: {
        address1: { $ref: "#/definitions/Address" },
        address2: { $ref: "#/definitions/Address" },
        lotsOfAddresses: {
          type: "array",
          items: { $ref: "#/definitions/Address" },
        },
      },
      additionalProperties: false,
      required: ["address1", "address2", "lotsOfAddresses"],
    };
@StefanTerdell
Copy link
Owner

I like the idea in principle, but I see a few issues:

  1. .brand() has a pretty substantial effect on your Zod schemas by essentially disabling duck-typing
  2. It would require a substantial refactoring of the current reference collection flow

How about something like this?

const myRecurringSchema = z.string()
const myObjectSchema = z.object({a: myRecurringSchema, b: myRecurringSchema})

const myJsonSchema = zodToJsonSchema(myObjectSchema, { definitions: { myRecurringSchema }})

With output

{
  "type": "object",
  "properties": {
    "a": {
      "$ref": "#/definitions/myRecurringSchema"
    },
    "b": {
      "$ref": "#/definitions/myRecurringSchema"
    }
  },
  "definitions": {
    "myRecurringSchema": {
      "type": "string"
    }
  }
}

This way we limit the blast radius by avoiding branding and can instantiate a References object "preloaded" with ref paths using the keys from the definitions option record

@StefanTerdell
Copy link
Owner

@bacebu4 Check the tests pr #28, let me know what you think

@bacebu4
Copy link
Author

bacebu4 commented Nov 28, 2022

@bacebu4 Check the tests pr #28, let me know what you think

Really liked the implementation, much better and cleaner than proposed solution

@StefanTerdell
Copy link
Owner

Released as 3.19.2

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants