GithubHelp home page GithubHelp logo

stefanterdell / zod-to-json-schema Goto Github PK

View Code? Open in Web Editor NEW
718.0 4.0 58.0 468 KB

Converts Zod schemas to Json schemas

License: ISC License

JavaScript 0.10% TypeScript 99.90%
zod json-schema

zod-to-json-schema's Introduction

Zod to Json Schema

NPM Version NPM Downloads

Looking for the exact opposite? Check out json-schema-to-zod

Summary

Does what it says on the tin; converts Zod schemas into JSON schemas!

  • Supports all relevant schema types, basic string, number and array length validations and string patterns.
  • Resolves recursive and recurring schemas with internal $refs.
  • Also able to target Open API 3 (Swagger) specification for paths.

Usage

import { z } from "zod";
import { zodToJsonSchema } from "zod-to-json-schema";

const mySchema = z
  .object({
    myString: z.string().min(5),
    myUnion: z.union([z.number(), z.boolean()]),
  })
  .describe("My neat object schema");

const jsonSchema = zodToJsonSchema(mySchema, "mySchema");

Expected output

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$ref": "#/definitions/mySchema",
  "definitions": {
    "mySchema": {
      "description": "My neat object schema",
      "type": "object",
      "properties": {
        "myString": {
          "type": "string",
          "minLength": 5
        },
        "myUnion": {
          "type": ["number", "boolean"]
        }
      },
      "additionalProperties": false,
      "required": ["myString", "myUnion"]
    }
  }
}

Options

Schema name

You can pass a string as the second parameter of the main zodToJsonSchema function. If you do, your schema will end up inside a definitions object property on the root and referenced from there. Alternatively, you can pass the name as the name property of the options object (see below).

Options object

Instead of the schema name (or nothing), you can pass an options object as the second parameter. The following options are available:

Option Effect
name?: string As described above.
basePath?: string[] The base path of the root reference builder. Defaults to ["#"].
$refStrategy?: "root" | "relative" | "seen" | "none" The reference builder strategy;
  • "root" resolves $refs from the root up, ie: "#/definitions/mySchema".
  • "relative" uses relative JSON pointers. See known issues!
  • "seen" reuses the output of any "seen" Zod schema. In theory it's a more performant version of "none", but in practice this behaviour can cause issues with nested schemas and has now gotten its own option.
  • "none" ignores referencing all together, creating a new schema branch even on "seen" schemas. Recursive references defaults to "any", ie {}.
Defaults to "root".
effectStrategy?: "input" | "any" The effects output strategy. Defaults to "input". See known issues!
dateStrategy?: "string" | "integer" Date strategy, integer allow to specify in unix-time min and max values. Defaults to "string".
emailStrategy?: "format:email" | "format:idn-email" | "pattern:zod" Choose how to handle the email string check. Defaults to "format:email".
definitionPath?: "definitions" | "$defs" The name of the definitions property when name is passed. Defaults to "definitions".
target?: "jsonSchema7" | "jsonSchema2019-09" | "openApi3" Which spec to target. Defaults to "jsonSchema7"
strictUnions?: boolean Scrubs unions of any-like json schemas, like {} or true. Multiple zod types may result in these out of necessity, such as z.instanceof()
definitions?: Record<string, ZodSchema> See separate section below
errorMessages?: boolean Include custom error messages created via chained function checks for supported zod types. See section below
markdownDescription?: boolean Copies the description meta to markdownDescription
patternStrategy?: "escape" | "preserve" Decide how patterns should be generated, for instance from z.string().includes("@"). By default "escape" is active and non-alphanumeric characters are escaped. This can sometimes cause issues with unicode flagged regex parsers.
pipeStrategy?: "all" | "input" | "output" Decide which types should be included when using z.pipe, for example z.string().pipe(z.number()) would return both string and number by default, only string for "input" and only number for "output".

Definitions

The definitions option lets you manually add recurring schemas into definitions for cleaner outputs. It's fully compatible with named schemas, changed definitions path and base path. Here's a simple example:

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

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

Result

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

Error Messages

This feature allows optionally including error messages created via chained function calls for supported zod types:

// string schema with additional chained function call checks
const EmailSchema = z.string().email("Invalid email").min(5, "Too short");

const jsonSchema = zodToJsonSchema(EmailSchema, { errorMessages: true });

Result

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "string",
  "format": "email",
  "minLength": 5,
  "errorMessage": {
    "format": "Invalid email",
    "minLength": "Too short"
  }
}

This allows for field specific, validation step specific error messages which can be useful for building forms and such. This format is accepted by react-hook-form's ajv resolver (and therefor ajv-errors which it uses under the hood). Note that if using AJV with this format will require enabling ajv-errors as vanilla AJV does not accept this format by default.

Custom Error Message Support

  • ZodString
    • regex
    • min, max
    • email, cuid, uuid, url
    • endsWith, startsWith
  • ZodNumber
    • min, max, lt, lte, gt, gte,
    • int
    • multipleOf
  • ZodSet
    • min, max
  • ZodArray
    • min, max

Known issues

  • When using .transform, the return type is inferred from the supplied function. In other words, there is no schema for the return type, and there is no way to convert it in runtime. Currently the JSON schema will therefore reflect the input side of the Zod schema and not necessarily the output (the latter aka. z.infer). If this causes problems with your schema, consider using the effectStrategy "any", which will allow any type of output.
  • JSON Schemas does not support any other key type than strings for objects. When using z.record with any other key type, this will be ignored. An exception to this rule is z.enum as is supported since 3.11.3
  • Relative JSON pointers, while published alongside JSON schema draft 2020-12, is not technically a part of it. Currently, most resolvers do not handle them at all.
  • Since v3, the Object parser uses .isOptional() to check if a property should be included in required or not. This has the potentially dangerous behavior of calling .safeParse with undefined. To work around this, make sure your preprocess and other effects callbacks are pure and not liable to throw errors. An issue has been logged in the Zod repo and can be tracked here.

Versioning

This package does not follow semantic versioning. The major and minor versions of this package instead reflects feature parity with the Zod package.

I will do my best to keep API-breaking changes to an absolute minimum, but new features may appear as "patches", such as introducing the options pattern in 3.9.1.

Changelog

https://github.com/StefanTerdell/zod-to-json-schema/blob/master/changelog.md

zod-to-json-schema's People

Contributors

andy2003 avatar bram-dc avatar ciscoheat avatar dependabot[bot] avatar iway1 avatar janpot avatar johngeorgewright avatar krzysztofciombor-solidstudio avatar leo-diehl avatar mokocm avatar mrhammadasif avatar noah2610 avatar planeshifter avatar scammi avatar stefanterdell avatar tomarad avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

zod-to-json-schema's Issues

Improvement request: support for MongoDB json schema

Hi,

Thanks for making this package. Looks super useful. I'd like to use it in a Mongo project but they have a slightly different expectation for json schemas. Would you consider adding Mongo support?

I think it would require a few changes. In particular:

  1. They recommend using bsonType instead of type
  2. They use int instead of integer
  3. They use bool instead of boolean
  4. z.date() would map to bsonType: 'date'

How can enum descriptions be supported?

import * as z from "zod";
import { zodToJsonSchema } from "zod-to-json-schema";

const mySchema = z.union([
  z.literal("foo").describe("I'm a foo"),
  z.literal("bar").describe("I'm a bar"),
]);

const jsonSchema = zodToJsonSchema(mySchema);

console.log(JSON.stringify(jsonSchema, null, 2));

results in

{
  "type": "string",
  "enum": [
    "foo",
    "bar"
  ],
  "$schema": "http://json-schema.org/draft-07/schema#"
}

The descriptions are lost.

Would it make sense to instead implement this as anyOf?

{
  "type": "string",
  "anyOf": [
    { "const": "foo", "description": "I'm a foo" },
    { "const": "bar", "description": "I'm a bar" }
  ],
  "$schema": "http://json-schema.org/draft-07/schema#"
}

No support for $ref

Hi, I appreciate the simplicity of this library. But without support for resolving $ref's, it makes it quite limited for anything other than simple schemas. Have you considered adding support for $ref?

Thanks :)

`ZodDiscriminatedUnionDef` broke in `zod` 3.20.0

Zod 3.20.0 was just released, and while it claims to have no breaking changes, ZodDiscriminatedUnionDef now only has two type arguments. Therefore, builds using zod-to-json-schema now fail with

node_modules/zod-to-json-schema/src/parsers/union.d.ts:22:58 - error TS2314: Generic type 'ZodDiscriminatedUnionDef<Discriminator, Options>' requires 2 type argument(s).

22 export declare function parseUnionDef(def: ZodUnionDef | ZodDiscriminatedUnionDef<any, any, any>, refs: References): JsonSchema7PrimitiveUnionType | JsonSchema7AnyOfType | undefined;

TypeScript: Type instantiation is excessively deep and possibly infinite

Simple reproduction:

Screenshot 2023-06-01 at 4 33 54 PM

Gives this error both in VSCode and on the CLI:

> deno check example.ts
error: TS2589 [ERROR]: Type instantiation is excessively deep and possibly infinite.
const json = zodToJsonSchema(MyObject, "MyObject")
             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

If anyone has a workaround I'd appreciate it. I'm trying to propose using zod-to-json-schema and it's going to be a no-go if it doesn't type-check. ๐Ÿ˜ฌ

Zod <-> JSON <-> Database possible here?

Hi I am researching tools for a survey-like application I will be building that requires user generated forms. I was wondering if something like this would be possible with this package where I would save the Zod equivalent JSON schema to the DB and later convert it back to Zod with React Hook Form to generate the form. Any thoughts would be helpful. Thank you!

Unions in intersections still generate with `"additionalProperties": false`

See also: #64, #65.

Reproduction:

const left = z.union([
  z.object({ field1: z.number() }),
  z.object({ field3: z.string() }),
]);
const right = z.object({ field2: z.boolean() });
const intersection = z.intersection(left, right);
const schema = zodToJsonSchema(intersection);

Generates the following schema:

{
  "allOf": [
    {
      "anyOf": [
        {
          "type": "object",
          "properties": { "field1": { "type": "number" } },
          "required": ["field1"],
          "additionalProperties": false
        },
        {
          "type": "object",
          "properties": { "field3": { "type": "string" } },
          "required": ["field3"],
          "additionalProperties": false
        }
      ]
    },
    {
      "type": "object",
      "properties": { "field2": { "type": "boolean" } },
      "required": ["field2"]
    }
  ],
  "$schema": "http://json-schema.org/draft-07/schema#"
}

Because of "additionalProperties": false, no object can be successfully validated against this schema:
https://www.jsonschemavalidator.net/s/ddO3vtM2

Compared to schema with removed additionalProperties:
https://www.jsonschemavalidator.net/s/UO8HV6Lz

Deno instructions

๐Ÿ‘‹

I notice that this module is published to deno.land/x, but i can not figure out how to get it working. Have fiddled with import map and npm:-specifiers to no avail.

Any pointers?

Date parser: support minimum and maximum dates check.

The current date parser resolves very basic information from the ZOD schema, the type and format.

Our current usage of this library would greatly be improved by being able to support min and max checks. For ZOD documentation reference see here.

Add support for branded types

Currently branded types are not supported.

Actual

zodToJsonSchema(z.string().brand<'color'>())

returns

{ "$schema": "http://json-schema.org/draft-07/schema#" }

Expected

zodToJsonSchema(z.string().brand<'color'>())

returns

{ "$schema": "http://json-schema.org/draft-07/schema#", "type": "string" }

Conditional validation: If/then/else

Hi,

Thanks for making this package. Looks super useful.
I am trying to create a complex Web Annotation JSON-LD schema. It involves some form of conditional validation, such as if property "A" has a specific value, property "B" should be required.

I tried to create 2 sets of subschema and combine them with a union using Zod.or, but it didn't work.

Is there any way to create conditional validation with this library?
Something that creates a similar output described on Avj (https://ajv.js.org/json-schema.html#if-then-else )

{
  type: "object",
  if: {properties: {foo: {minimum: 10}}},
  then: {required: ["bar"]},
  else: {required: ["baz"]}
}

Add support for custom types

Unsure if it's by design, or if I've misunderstood it's use, but it seems that zod-to-json-schema ignores "custom" types:

import { z } from 'zod'
import zodToJsonSchema from 'zod-to-json-schema'

const schema = z.array(z.custom<string>((x) => typeof x === 'string'))
console.info(zodToJsonSchema(schema))

Outputs:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "array",
  "items": {}
}

Would be very helpful if this was supported.

Schema for intersection types are invalid

Given:

const schema1 = z.object({
    foo: z.string()
});
const schema2= z.object({
    bar: z.string()
});
const intersection = z.intersection(schema1, schema2);
const jsonSchema = parseIntersectionDef(intersection._def, getRefs());

the following schema is generated:

{
    "allOf": [
        {
            "additionalProperties": false,
            "properties": {
                "foo": {
                    "type": "string"
                }
            },
            "required": [
                "foo"
            ],
            "type": "object"
        },
        {
            "additionalProperties": false,
            "properties": {
                "bar": {
                    "type": "string"
                }
            },
            "required": [
                "bar"
            ],
            "type": "object"
        }
    ]
}

The following input:

{ foo: 'foo', bar: 'bar' }

results in an validation error:

image

The Problem is, that the additionalProperties in both allOf-schemas resullts in failing each separate sub-schema validation. This seems to be a common problem in JsonSchema-Validation.

The JSON-Schema which is validating everything as expected looks like:

{
  "allOf": [
    {
      "type": "object",
      "properties": {
        "foo": {
          "type": "string"
        }
      },
      "required": [
        "foo"
      ],
    },
    {
      "type": "object",
      "properties": {
        "bar": {
          "type": "string"
        }
      },
      "required": [
        "bar"
      ],
    }
  ],
  "$schema": "http://json-schema.org/draft-07/schema#",
  "unevaluatedProperties": false
}

Note the unevaluatedProperties and the missing additionalProperties.

image

This issue was found in backstage/backstage#17721

Add Support for z.input / z.output

Currently, this package seems to only be able to generate z.input. Thereโ€™s differences between the two when using .default() for example.

Custom Error Messages?

zod provides mechanisms for easily creating validation-step-specific error messages, which can be used to easily show human-friendly error messages to either developers or even end users when they submit a form. When this library translates a zod schema to JSON schema, it throws away any custom error message. IE this:

const schema = z.object({
   string: z.string().min(1, "Too short"),
});

Translates to:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "string": {
      "type": "string",
      "minLength": 1
    }
  },
  "required": [
    "string"
  ],
  "additionalProperties": false
} 

Notice the error message is simply thrown away. It would be very useful to allow users to include these error messages in the schema (in case the developer would like to show them to a user, for example).

The JSON Schema specification does not specifically require or prescribe an ability to implement error messages, so it is up to the developers to come up with an implementation as there is no standard. There are a couple of different implementations out there. For example AJV has a specification for their ajv-errors package https://ajv.js.org/packages/ajv-errors.html

It looks something like this:

const schema = {
  type: "object",
  required: ["foo", "bar"],
  allOf: [
    {
      properties: {
        foo: {type: "integer", minimum: 2},
        bar: {type: "string", minLength: 2},
      },
      additionalProperties: false,
    },
  ],
  errorMessage: {
    properties: {
      foo: "data.foo should be integer >= 2",
      bar: "data.bar should be string with length >= 2",
    },
  },
}

So we'd have an "errorMessage" property at the top level of the schema describing which error message to show when some validation for some field fails.

I'm really not a fan of this one and don't think it'd map well from zod, because it doesn't allow for a custom error message per validation step, which is extremely important in achieving great UX when building forms and the like. IE if you had this schema:

z.object({
    property: z.string().min(1, "Field can't be empty").max(8, "Field must be 8 characters or shorter")
})

There would be no way to show each error message when appropriate.

An approach that seems much better is shown in react-hook-forms ajv resolver, funny enough: https://github.com/react-hook-form/resolvers

const schema = {
  type: 'object',
  properties: {
    username: {
      type: 'string',
      minLength: 1,
      errorMessage: { minLength: 'username field is required' },
    },
    password: {
      type: 'string',
      minLength: 1,
      errorMessage: { minLength: 'password field is required' },
    },
  },
  required: ['username', 'password'],
  additionalProperties: false,
};

Here we have an "errorMessage" object per property, allowing us to specify messages per validation step. This maps basically one to one with how we write error messages in zod and so IMO it makes a lot of sense to use this approach, and selfishly it would work out the box with react-hook-form which is how I'm wanting to use it.

Would probably be an opt-in feature of course.

Is this something we want? I'd be willing to create a PR if this is something other's would find valuable

Support for JSONSchema4 types were dropped in 3.20.0

It seems like since 3.20.0, even if you specify openApi3 instead of jsonSchema7, the return type is still JsonSchema7Type

image

Since ESLint's schema option supports only JSONSchema4, I'm unable to upgrade to draft-07 which leads to the following type error:

Type 'JsonSchema7Type & { $schema?: string | undefined; definitions?: { [key: string]: JSONSchema7Type; } | undefined; }' is not assignable to type 'JSONSchema4 | readonly JSONSchema4[]'.

image

This is the change:

- function zodToJsonSchema<
-   Name extends string | undefined = undefined,
-   Strategy extends "root" | "relative" | "none" | undefined = undefined,
-   BasePath extends string[] | undefined = undefined,
-   DefinitionPath extends "definitions" | "$defs" = "definitions",
-   Target extends "jsonSchema7" | "openApi3" | undefined = undefined,
-   Definitions extends Record<string, ZodSchema<any>> | undefined = undefined
- >(
-   schema: ZodSchema<any>,
-   options?: {
-     name?: Name;
-     $refStrategy?: Strategy;
-     basePath?: BasePath;
-     effectStrategy?: EffectStrategy;
-     definitionPath?: DefinitionPath;
-     target?: Target;
-     strictUnions?: boolean;
-     definitions?: Definitions;
-   }
- ): Target extends "openApi3"
-   ? Name extends string
-     ? BasePath extends string[]
-       ? {
-           $ref: string;
-         } & Record<DefinitionPath, Record<Name | keyof Definitions, object>>
-       : Strategy extends "relative"
-       ? {
-           $ref: `0/${DefinitionPath}/${Name}`;
-         } & Record<DefinitionPath, Record<Name | keyof Definitions, object>>
-       : {
-           $ref: `#/${DefinitionPath}/${Name}`;
-         } & Record<DefinitionPath, Record<Name | keyof Definitions, object>>
-     : object
-   : Name extends string
-   ? BasePath extends string[]
-     ? {
-         $schema: "http://json-schema.org/draft-07/schema#";
-         $ref: string;
-       } & Record<
-         DefinitionPath,
-         Record<Name | keyof Definitions, JsonSchema7Type>
-       >
-     : Strategy extends "relative"
-     ? {
-         $schema: "http://json-schema.org/draft-07/schema#";
-         $ref: `0/${DefinitionPath}/${Name}`;
-       } & Record<
-         DefinitionPath,
-         Record<Name | keyof Definitions, JsonSchema7Type>
-       >
-     : {
-         $schema: "http://json-schema.org/draft-07/schema#";
-         $ref: `#/${DefinitionPath}/${Name}`;
-       } & Record<
-         DefinitionPath,
-         Record<Name | keyof Definitions, JsonSchema7Type>
-       >
-   : Definitions extends undefined
-   ? { $schema: "http://json-schema.org/draft-07/schema#" } & JsonSchema7Type
-   : { $schema: "http://json-schema.org/draft-07/schema#" } & JsonSchema7Type &
-       Record<DefinitionPath, Record<keyof Definitions, JsonSchema7Type>>;

Bug: `z.string().nullable()` converts to `any`

Repro

  1. Define one of the types below
z.string().nullable()
z.union([z.string(), z.null()])

as well as a control case

z.number().nullable()
  1. Convert to JSON schema.

Expected

Similar to z.number().nullable(), the type should have been string | null.

Observed

The type is any.

z.set should enforce items uniqueness

One of the set's valuable points is its items' uniqueness. I suggest interpreting z.set as unique items array.

For example, let's say I have a schema for filter validation. It has status property which could be an array of possible statuses.

export const statuses = z.enum([
	"draft",
	"submitted",
	"approved",
	"rejected",
	"archived",
]);

export const form = z
	.object({
		status: z.set(statuses),
	})
	.partial();

Since this is used in filtering, I would expect to be able to filter items by multiple statuses. The following snippet shows how I would expect schema to look like:

{
  // ...
  "status": {
    "type": "array",
    "uniqueItems": true, // <- this property is not present in the current output
    "items": {
      "type": "string",
      "enum": [
          "draft",
          "submitted",
          "approved",
          "rejected",
          "archived"
      ]
    }
  }
}

Let me know what You think about this feature.

Remove empty anyOf

I have the following schema to add support for mongodb ObjectId type:

export const objectId = z.union([
  z.instanceof(ObjectId),
  z
    .string()
    .regex(/^[a-f0-9]{24}$/, { message: "This string does not look like an ObjectId()" })
    .transform((s) => new ObjectId(s))
]);

This translates to the following schema:

{
  anyOf: [ {}, { type: 'string', pattern: '^[a-f0-9]{24}$' } ]
}

The issue is that {} allows for any input. I think it would be more appropriate to imply that z.instanceOf() cannot be translated into a JSON Schema and therefore should never be able to match anything somehow.

Does that make any sense?

Add a configuration for ZodPipeline

The parser for the ZodPipeline could be configurable like the one for ZodEffects. Sometimes the pipeline is only an internal transformation we may not want in the generated schema.

Currently, it works as follows:

const dateModel = z.object({
  year: z.number().min(2021).max(2023),
  month: z.number().min(1).max(12),
  day: z.number().min(1).max(31),
})

const stringInputDate = z
  .string()
  .regex(/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/)
  .transform((date) => {
    const [year, month, day] = date.split('-')

    return {
      year: Number(year),
      month: Number(month),
      day: Number(day),
    }
  }).pipe(dateModel)

const schema = zodToJsonSchema(stringInputDate)

which will generate something like:

{
  "allOf": [
    { "type": "string", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$" },
    {
      "type": "object",
      "properties": {
        "year": { "type": "number", "minimum": 2021, "maximum": 2023 },
        "month": { "type": "number", "minimum": 1, "maximum": 12 },
        "day": { "type": "number", "minimum": 1, "maximum": 31 }
      },
      "required": ["year", "month", "day"],
      "additionalProperties": false
    }
  ],
  "$schema": "http://json-schema.org/draft-07/schema#"
}

I propose the addition of a pipelineStrategy: 'input' | 'all' which would then make the output be:

{
  "type": "string",
  "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$",
  "$schema": "http://json-schema.org/draft-07/schema#"
}

if the input strategy was selected.

Reused schema generates invalid $ref (?)

[email protected]

const legalReasonSchema = z
  .object({
    reason: z.enum(['FOO', 'BAR']),
  })
  .strict();

const identityRequestSchema = z
  .object({
    alias: z
      .object({
        legalReason: legalReasonSchema.nullish(), // reused here
      })
      .strict(),
    requiredLegalReasonTypes: z.array(legalReasonSchema.shape.reason).nullish(),  // reused here  
  })
  .strict();
 
zodToJsonSchema(identityRequestSchema, {
    target: 'openApi3',
}); 

/*
=>
...
"requiredLegalReasonTypes": {
  "type": "array",
  "items": {
     "$ref": "#/properties/alias/properties/legalReason/anyOf/0/properties/reason"
   },
   "nullable": true
},
...
*/

Not sure if I am not misunderstanding something, but I would assume it should not generate any $refs unless I use seen/relative or provide definitions option.

`Maximum call stack size exceeded` when using a generic recursive schema

Hi, thanks for this great library! Please consider the following code:

import { zodToJsonSchema } from "zod-to-json-schema";
import { z } from "zod";

const FooSchema = z.object({
  foo: z.string(),
});

type Hierarchy<T> = {
  item: T;
  children: Hierarchy<T>[];
};

const createHierarchySchema = <T>(
  itemSchema: z.ZodSchema<T>
): z.ZodType<Hierarchy<T>> => {
  return z.lazy(() =>
    z.object({
      item: itemSchema,
      children: z.array(createHierarchySchema(itemSchema)),
    })
  ) as unknown as z.ZodType<Hierarchy<T>> // Type-problem reported here https://github.com/colinhacks/zod/issues/2008;
};

const HierarchySchema = createHierarchySchema(FooSchema);

console.log(JSON.stringify(zodToJsonSchema(HierarchySchema)));

Instead of getting a json-schema, the code throws an error RangeError: Maximum call stack size exceeded. Is this a bug or is my use-case not supported?

Support for `.refine` to use `if` in generated json schema

Hey, first of thanks for this amazing package ๐Ÿ™๐Ÿฝ

I'd love to have support for .refine to generate corresponding if condition in the json schema

Example schema

const someSchema = z
  .object({
    enable: z.boolean().default(false),
    id: z.string().optional(),
  })
  .refine((data) => {
    if (data.enable && !data.id) {
      return false;
    }

    return true;
  }, "id should be specified");

generated

{
  "type": "object",
  "properties": {
    "enable": {
      "type": "boolean",
      "default": false
    },
    "id": {
      "type": "string"
    }
  },
  "additionalProperties": false,
  "$schema": "http://json-schema.org/draft-07/schema#"
}

expected

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "enable": {
      "type": "boolean",
      "default": false
    },
    "id": {
      "type": "string"
    }
  },
  "if": {
    "properties": {
      "enable": {
        "const": true
      }
    }
  },
  "then": {
    "required": ["id"]
  },
  "else": {
    "properties": {
      "id": {
        "type": "string"
      }
    }
  }
}

Let me know if there's a workaround for it too. Thanks!

TypeScript Union should be `oneOf` in JSON Schema

Current Behavior

z.union([z.number(), z.string()] produces:

{
  "anyOf": [
    {
      "type": "array",
      "items": {
        "type": "number",
      },
    },
    {
      "type": "array",
      "items": {
        "type": "string",
      },
    },
  ],
}

Expected Behavior

It should produce oneOf.

Cyclic references within definitions

Representation example

Given the input

interface User {
  id: string;
  headUser?: User;
}

export const userSchema: z.ZodType<User> = z.lazy(() =>
  z.object({
    id: z.string(),
    headUser: userSchema.optional()
  })
);

Produces the following output

{
    "$schema": "http://json-schema.org/draft-07/schema#",
    "type": "object",
    "properties": {
        "user": {
            "$ref": "#/properties/headUser"
        }
    },
    "required": [
        "user"
    ],
    "additionalProperties": false,
    "definitions": {
        "User": {
            "type": "object",
            "properties": {
                "id": {
                    "type": "string"
                },
                "headUser": {
                    "type": "object",
                    "properties": {
                        "id": {
                            "type": "string"
                        },
                        "headUser": {
                            "$ref": "#/properties/headUser"
                        }
                    },
                    "required": [
                        "id"
                    ],
                    "additionalProperties": false
                }
            },
            "required": [
                "id"
            ],
            "additionalProperties": false
        }
    }
}

option to add `markdownDescription` for use in vscode

Summary

vscode can render JSON schema description in markdown using markdownDescription field.
it'd be great if zod-to-json-schema could support markdownDescription by copying description field.

Example

before with markdownDescription
{
  type: "array",
  items: {
    anyOf: [
      { type: "string" },
      {
        type: "string",
        const: "NO_DISPLAY",
        description: "This vitamin will not be shown when examining a food."
      }
    ]
  },
  "$schema": "http://json-schema.org/draft-07/schema#"
}
{
  type: "array",
  items: {
    anyOf: [
      { type: "string" },
      {
        type: "string",
        const: "NO_DISPLAY",
        description: "This vitamin will not be shown when examining a food."
		markdownDescription: "This vitamin will not be shown when examining a food."
      }
    ]
  },
  "$schema": "http://json-schema.org/draft-07/schema#"
}

Improve definitions for mutual recursive types

Consider generating json-schema as follows:

import * as z from "zod";
import { zodToJsonSchema } from "zod-to-json-schema";

const leafSchema = z.object({
  prop: z.string(),
});

let nodeChildSchema: z.ZodType;

const nodeSchema = z.object({
  children: z.lazy(() => z.array(nodeChildSchema)),
});

nodeChildSchema = z.union([leafSchema, nodeSchema]);

const treeSchema = z.object({
  nodes: nodeSchema,
});

const jsonSchema = zodToJsonSchema(treeSchema, {
  name: "Tree",
  definitions: {
    Leaf: leafSchema,
    NodeChild: nodeChildSchema,
    Node: nodeSchema,
  },
});

console.log(JSON.stringify(jsonSchema, null, 2));

Currently this results in:

{
  "$ref": "#/definitions/Tree",
  "definitions": {
    "Leaf": {
      "type": "object",
      "properties": {
        "prop": {
          "type": "string"
        }
      },
      "required": [
        "prop"
      ],
      "additionalProperties": false
    },
    "NodeChild": {
      "anyOf": [
        {
          "$ref": "#/definitions/Leaf"
        },
        {
          "type": "object",
          "properties": {
            "children": {
              "type": "array",
              "items": {
                "$ref": "#/definitions/NodeChild"
              }
            }
          },
          "required": [
            "children"
          ],
          "additionalProperties": false
        }
      ]
    },
    "Node": {
      "$ref": "#/definitions/NodeChild/anyOf/1"
    },
    "Tree": {
      "type": "object",
      "properties": {
        "nodes": {
          "$ref": "#/definitions/NodeChild/anyOf/1"
        }
      },
      "required": [
        "nodes"
      ],
      "additionalProperties": false
    }
  },
  "$schema": "http://json-schema.org/draft-07/schema#"
}

But that's not the schema I would expect from that input. Specifically, I expect the Node definition to not be a reference, but instead:

{
  "$ref": "#/definitions/Tree",
  "definitions": {
    "Leaf": {
      "type": "object",
      "properties": {
        "prop": {
          "type": "string"
        }
      },
      "required": ["prop"],
      "additionalProperties": false
    },
    "Node": {
      "type": "object",
      "properties": {
        "children": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/NodeChild"
          }
        }
      },
      "required": ["children"],
      "additionalProperties": false
    },
    "NodeChild": {
      "anyOf": [
        {
          "$ref": "#/definitions/Leaf"
        },
        {
          "$ref": "#/definitions/Node"
        }
      ]
    },
    "Tree": {
      "type": "object",
      "properties": {
        "nodes": {
          "$ref": "#/definitions/Node"
        }
      },
      "required": ["nodes"],
      "additionalProperties": false
    }
  },
  "$schema": "http://json-schema.org/draft-07/schema#"
}

I also noticed that

const jsonSchema = zodToJsonSchema(treeSchema, {
  name: "Tree",
  definitions: {
    Leaf: leafSchema,
    NodeChild: nodeChildSchema,
    Node: nodeSchema,
  },
});

and

const jsonSchema = zodToJsonSchema(treeSchema, {
  name: "Tree",
  definitions: {
    Leaf: leafSchema,
    Node: nodeSchema,
    NodeChild: nodeChildSchema,
  },
});

produce different results.

ZodDiscriminatedUnionDef

tsc -b

node_modules/zod-to-json-schema/src/parsers/union.d.ts:22:58 - error TS2707: Generic type 'ZodDiscriminatedUnionDef<Discriminator, Options>' requires between 1 and 2 type arguments.

22 export declare function parseUnionDef(def: ZodUnionDef | ZodDiscriminatedUnionDef<any, any, any>, refs: References): JsonSchema7PrimitiveUnionType | JsonSchema7AnyOfType | undefined;
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Zod optional not working on non object property values

I am trying to use zod's optional z.optional() but it don't seem to heve any effect on basic types.

zodToJsonSchema(z.number())

// {
//     "$schema": "http://json-schema.org/draft-07/schema#",
//     "type": "number"
// }

zodToJsonSchema(z.optional(z.number()))

// {
//     "$schema": "http://json-schema.org/draft-07/schema#",
//     "type": "number"
// }

but it works correctly on object properties

zodToJsonSchema(z.object({
    username: z.string(),
}))

// {
//     "$schema": "http://json-schema.org/draft-07/schema#",
//     "type": "object",
//     "properties": {
//         "username": {
//             "type": "string"
//         }
//     },
//     "required": [
//         "username"
//     ],
//     "additionalProperties": false
// }

zodToJsonSchema(z.object({
    username: z.string().optional(),
}))

// {
//     "$schema": "http://json-schema.org/draft-07/schema#",
//     "type": "object",
//     "properties": {
//         "username": {
//             "type": "string"
//         }
//     },
//     "additionalProperties": false
// }

I Would expect it to produce a union of undefined and given type, which is the same result as the TypeScript type that is inferred.

const zodType = z.optional(z.number())
type exampleType = z.infer<typeof zodType>
// exampleType type: number | undefined

Output should be probably the same as of this code:

zodToJsonSchema(z.union([z.undefined(), z.number()]))
// {
//     "$schema": "http://json-schema.org/draft-07/schema#",
//     "anyOf": [
//         {
//             "not": {}
//         },
//         {
//             "type": "number"
//         }
//     ]
// }

Move zod to peerDependencies

Thanks for the great package!

Typescript isn't happy if you list both zod and zod-to-json-schema in your package.json and the resolved versions are slightly off. Would it make sense to move zod to peerDependencies?

Any way to add style: deepObject?

          type: object
          properties:
            page:
              type: number
              minimum: 0
            limit:
              type: number
              maximum: 100
          additionalProperties: false
        in: query
        name: pagination
        required: true
        style: deepObject # <----------```

Allow customization of meta

We are planning on putting a JSON.stringified object into the description field in zod and would like to use it to customize the display of things like title and description. Would it be possible to pass an option and allow the schema to be customized as you walk down the tree?

zodToJsonSchema tries to evaluate .preprocess with `undefined` passed in

To reproduce, use https://github.com/Firfi/zod-to-json-schema-preprocess-bug

For such code as

const idParser = z.preprocess(
  (uuid) => {
    if (typeof uuid !== 'string') throw new Error(`Expected string, got ${typeof uuid}: ${uuid}`);
    const [type, id] = uuid.split('SEPARATOR');
    return { type, id };
  }, z.object({ type: z.string(), id: z.string() }));

zodToJsonSchema(idParser) will be evaluated all right, however

if I do

const idParser2 = z.object({
  id: idParser
})

zodToJsonSchema(idParser2) starts passing undefined into preprocess (as my uuid var)

I wonder if that's expected behaviour and why it happens only when a parser is used inside z.object

exclusiveMinimum and exclusiveMaximum should be boolean, not number

According to https://json-schema.org/understanding-json-schema/reference/numeric.html

In JSON Schema Draft 4, exclusiveMinimum and exclusiveMaximum work differently. There they are boolean values, that indicate whether minimum and maximum are exclusive of the value

While trying to match its return type with @typescript-eslint/utils.RuleMetaData.schema, I'm getting:

Type '{ $schema: "http://json-schema.org/draft-07/schema#"; } & JsonSchema7Type' is not assignable to type 'JSONSchema4 | readonly JSONSchema4[]'.
  Type '{ $schema: "http://json-schema.org/draft-07/schema#"; } & JsonSchema7NumberType & { default?: any; description?: string | undefined; }' is not assignable to type 'JSONSchema4 | readonly JSONSchema4[]'.
    Type '{ $schema: "http://json-schema.org/draft-07/schema#"; } & JsonSchema7NumberType & { default?: any; description?: string | undefined; }' is not assignable to type 'JSONSchema4'.
      Types of property 'exclusiveMaximum' are incompatible.
        Type 'number | undefined' is not assignable to type 'boolean | undefined'.
          Type 'number' is not assignable to type 'boolean | undefined'.ts(2322)

Handling of differences between Zod and Json Schema

Hi!

When looking through the json schema specs on emails and how zod handles them I realised there's a fundamental difference: while the json schema spec uses RFC 5321 for email and RFC 6531 for idn-email, zod uses a custom RegExp which it deems "reasonable".
This is where I wondered if matching zod.string().email() to "format": "email" actually makes sense, or if we should maybe use "pattern": "[regex from zod]" instead. Otherwise one could run into issues where an email is valid for the json schema, but invalid when using zod.

This issue extends to #40, where I'm currently adding RegExps for the aforementioned RFCs - which wouldn't be in line with how zod handles things and leads to enormous patterns being ejected.

I think this deserves a general answer, with options being

  1. try to generate a json schema which best contains the information given to the zod schema (use "format": "idn-email")
  2. generate a json schema which matches with the validations zod does (use "pattern": "[regex from zod]")

@StefanTerdell

[Question] Discriminated union serializes differently extended schemas

The following code generates different schemas:

import { z } from "zod";
import zodToJsonSchema from "zod-to-json-schema";


const GENERIC_SCHEMA = z.object({
    dummy: z.string()
});

const SCHEMA_A = GENERIC_SCHEMA.extend({
    type: z.literal("A")
});

const SCHEMA_B = GENERIC_SCHEMA.extend({
    type: z.literal("B")
});

const SCHEMA_UNION1 = z.discriminatedUnion("type", [SCHEMA_A, SCHEMA_B])


const SCHEMA_C = z.object({
    dummy: z.string(),
    type: z.literal("C")
});

const SCHEMA_D = z.object({
    dummy: z.string(),
    type: z.literal("D")
});

const SCHEMA_UNION2 = z.discriminatedUnion("type", [SCHEMA_C, SCHEMA_D])


const GENERIC_OBJECT = {
    dummy: z.string()
}
const SCHEMA_E = z.object({
    ...GENERIC_OBJECT,
    type: z.literal("E")
});

const SCHEMA_F = z.object({
    ...GENERIC_OBJECT,
    type: z.literal("F")
});

const SCHEMA_UNION3 = z.discriminatedUnion("type", [SCHEMA_E, SCHEMA_F])


const json1 = zodToJsonSchema(SCHEMA_UNION1);
const json2 = zodToJsonSchema(SCHEMA_UNION2);
const json3 = zodToJsonSchema(SCHEMA_UNION3);
console.dir(json1, {depth: null})
console.log("==================")
console.dir(json2, {depth: null})
console.log("==================")
console.dir(json3, {depth: null})

I would expect this to generate the same kind of schema but here is what I get as output:

{
  anyOf: [
    {
      type: 'object',
      properties: {
        dummy: { type: 'string' },
        type: { type: 'string', const: 'A' }
      },
      required: [ 'dummy', 'type' ],
      additionalProperties: false
    },
    {
      type: 'object',
      properties: {
        dummy: { '$ref': '#/anyOf/0/properties/dummy' }, <===== THIS
        type: { type: 'string', const: 'B' }
      },
      required: [ 'dummy', 'type' ],
      additionalProperties: false
    }
  ],
  '$schema': 'http://json-schema.org/draft-07/schema#'
}
==================
{
  anyOf: [
    {
      type: 'object',
      properties: {
        dummy: { type: 'string' },
        type: { type: 'string', const: 'C' }
      },
      required: [ 'dummy', 'type' ],
      additionalProperties: false
    },
    {
      type: 'object',
      properties: {
        dummy: { type: 'string' },
        type: { type: 'string', const: 'D' }
      },
      required: [ 'dummy', 'type' ],
      additionalProperties: false
    }
  ],
  '$schema': 'http://json-schema.org/draft-07/schema#'
}
==================
{
  anyOf: [
    {
      type: 'object',
      properties: {
        dummy: { type: 'string' },
        type: { type: 'string', const: 'E' }
      },
      required: [ 'dummy', 'type' ],
      additionalProperties: false
    },
    {
      type: 'object',
      properties: {
        dummy: { '$ref': '#/anyOf/0/properties/dummy' }, <===== THIS
        type: { type: 'string', const: 'F' }
      },
      required: [ 'dummy', 'type' ],
      additionalProperties: false
    }
  ],
  '$schema': 'http://json-schema.org/draft-07/schema#'
}

As you can see the unions that had extended schemas have generated one of the objects with a $ref. This caused openapi errors in a project of mine which I fixed by just copy pasting the shared properties between schemas instead of extending a generic one. I suspect this is the expected behaviour of the conversion but would you suggest a way to share properties to keep the attributes DRY?

Edit:
Closing as I have clearly completely missed the point of the refStrategy option..

Error: Cannot find module './refs'

Looks like the latest 3.20.0 release has an issue with the import of ./refs

Running the following example command on the terminal

node -e "const convert = require('zod-to-json-schema'); const { z } = require('zod'); console.log(convert.default(z.object({ test: z.string() })))"

gives me

node:internal/modules/cjs/loader:936
  throw err;
  ^

Error: Cannot find module './refs'
Require stack:
- /home/satnam/work/it/dashboard-lib/node_modules/zod-to-json-schema/src/zodToJsonSchema.js
- /home/satnam/work/it/dashboard-lib/node_modules/zod-to-json-schema/index.js
- /home/satnam/work/it/dashboard-lib/[eval]
    at Function.Module._resolveFilename (node:internal/modules/cjs/loader:933:15)
    at Function.Module._load (node:internal/modules/cjs/loader:778:27)
    at Module.require (node:internal/modules/cjs/loader:1005:19)
    at require (node:internal/modules/cjs/helpers:102:18)
    at Object.<anonymous> (/home/satnam/work/it/dashboard-lib/node_modules/zod-to-json-schema/src/zodToJsonSchema.js:5:16)
    at Module._compile (node:internal/modules/cjs/loader:1101:14)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1153:10)
    at Module.load (node:internal/modules/cjs/loader:981:32)
    at Function.Module._load (node:internal/modules/cjs/loader:822:12)
    at Module.require (node:internal/modules/cjs/loader:1005:19) {
  code: 'MODULE_NOT_FOUND',
  requireStack: [
    '/home/satnam/work/it/dashboard-lib/node_modules/zod-to-json-schema/src/zodToJsonSchema.js',
    '/home/satnam/work/it/dashboard-lib/node_modules/zod-to-json-schema/index.js',
    '/home/satnam/work/it/dashboard-lib/[eval]'
  ]
}

Don't add definitions in the schema if they're not used

Considering the following snippet:

import * as z from "zod";
import { zodToJsonSchema } from "zod-to-json-schema";

const definition1 = z.string();

const definition2 = z.number();

const mySchema = z.object({
  prop: definition2,
});

const jsonSchema = zodToJsonSchema(mySchema, {
  definitions: {
    definition1,
    definition2,
  },
});

console.log(JSON.stringify(jsonSchema, null, 2));

This produces

{
  "type": "object",
  "properties": {
    "prop": {
      "$ref": "#/definitions/definition2"
    }
  },
  "required": [
    "prop"
  ],
  "additionalProperties": false,
  "definitions": {
    "definition1": {
      "type": "string"
    },
    "definition2": {
      "type": "number"
    }
  },
  "$schema": "http://json-schema.org/draft-07/schema#"
}

with a definition for definition1, even though it's not referenced anywhere. This is cumbersome in the scenario where I want to generate multiple schema's from zod types that may or may not use a common set of zod types.

Make it compatible with v3 of Zod

As Zod is now shifted to v3 - Stable, we should make it compatible with the new version

Currently, following type error is being returned

Argument of type 'ZodType<T, ZodTypeDef, T>' is not assignable to parameter of type 'ZodType<any, ZodTypeDef>'.
  Property 'toJSON' is missing in type 'ZodType<T, ZodTypeDef, T>' but required in type 'ZodType<any, ZodTypeDef>'

Order of optional and nullable matters

Hi, thank you for the library. The shape of the openApi3 schema depends on the application order of .nullable and .optional. I assume both versions are functionally identical. Unfortunatelly nullish produces the less favorable version :( Is this expected?

z.object({
  nullableOptional: z.number().nullable().optional(),
  optionalNullable: z.number().optional().nullable(),
  nullish: z.number().nullish(),
})

=>

{
  "nullableOptional": {
    "type": "number",
    "nullable": true
  },
  "optionalNullable": {
    "anyOf": [
      {
        "not": {}
      },
      {
        "type": "number"
      }
    ],
    "nullable": true
  },
  "nullish": {
    "anyOf": [
      {
        "not": {}
      },
      {
        "type": "number"
      }
    ],
    "nullable": true
  }
}

error: module not found `parseDef`

Platform Version
Deno 3.20.0
// deno run --allow-all test.ts
import { z } from 'https://deno.land/x/[email protected]/index.ts';

// import { zodToJsonSchema } from 'npm:zod-to-json-schema@latest';
import { zodToJsonSchema } from 'https://deno.land/x/[email protected]/zodToJsonSchema.ts';

const schema = z.string();

const json = zodToJsonSchema(schema);

console.log(json);

When I run the code shown above, I get the below error.

error: Module not found "https://deno.land/x/[email protected]/parseDef".
    at https://deno.land/x/[email protected]/zodToJsonSchema.ts:2:43

However, when i use npm:zod-to-json-schema@latest, I get the correct output shown below.

{ type: "string", "$schema": "http://json-schema.org/draft-07/schema#" }

(p.s. might be similar to #33)

zodToJsonSchema() - Can't get the correct describe of optional() schema

The zodToJsonSchema works fine to get the correct describe text if the property is not optional.

const TestSchema = z.object({ title: z.string().min(1) });
const TheObjectSchema = z.object({
  p1: TestSchema.describe('aaaaaaaaa'),
  p2: TestSchema.describe('bbbbbbbbb'),
  p3: TestSchema.describe('ccccccccc'),
})
  .describe('sssssssss');

const schema = zodToJsonSchema(TheObjectSchema, {
  target: 'openApi3',
  $refStrategy: 'none',
});
console.log((schema as any).properties);

After running this code you can get the result correctly.

{
  p1: {
    type: 'object',
    properties: { title: [Object] },
    required: [ 'title' ],
    additionalProperties: false,
    description: 'aaaaaaaaa'
  },
  p2: {
    type: 'object',
    properties: { title: [Object] },
    required: [ 'title' ],
    additionalProperties: false,
    description: 'bbbbbbbbb'
  },
  p3: {
    type: 'object',
    properties: { title: [Object] },
    required: [ 'title' ],
    additionalProperties: false,
    description: 'ccccccccc'
  }
}

You can see that the descriptions are what we want.

But if you change it to optional(), it will fail.

const TestSchema = z.object({ title: z.string().min(1) });
const TheObjectSchema = z.object({
  p1: TestSchema.optional().describe('aaaaaaaaa'),
  p2: TestSchema.optional().describe('bbbbbbbbb'),
  p3: TestSchema.optional().describe('ccccccccc'),
})
  .describe('sssssssss');

const schema = zodToJsonSchema(TheObjectSchema, {
  target: 'openApi3',
  $refStrategy: 'none',
});
console.log((schema as any).properties);

After running this code, you will get the following result.

{
  p1: {
    type: 'object',
    properties: { title: [Object] },
    required: [ 'title' ],
    additionalProperties: false,
    description: 'ccccccccc'
  },
  p2: {
    type: 'object',
    properties: { title: [Object] },
    required: [ 'title' ],
    additionalProperties: false,
    description: 'ccccccccc'
  },
  p3: {
    type: 'object',
    properties: { title: [Object] },
    required: [ 'title' ],
    additionalProperties: false,
    description: 'ccccccccc'
  }
}

You can see, all descriptions are the same, seems like it used the latest description, which is 'ccccccccc'.

Can I get someone to fix that? Or do you have any idea to avoid this error?

I am using the latest version (3.21.1).

Record with enum key type returns invalid OpenAPI

z.record(z.enum(["a", "b", "c"]), z.number().int());

returns

{
  "type": "object",
  "additionalProperties": {
    "type": "integer"
  },
  "propertyNames": {
    "enum": ["a", "b", "c"]
  }
}

for openapi target.

This makes the result spec invalid. According to this, propertyNames is not supported in openapi

The same happens for any records actually, which return "propertyNames": {} leading to invalid spec

image

Add support for "custom" definitions

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"],
    };

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.