JSON Schema package upgrade

GitHub profile picture of fabian-hiller

Valibot's JSON Schema package has seen significant growth in adoption over the past few weeks, reaching almost 200,000 monthly downloads on npm. We believe it is particularly popular for documentation and code generation purposes via the OpenAPI specification, as well as for generating structured LLM outputs.

As these use cases will probably become more common in future, we have listened to your feedback and decided to invest more time in developing the package to make it extremely powerful. This blog post will introduce the new features added in the last two minor versions.

Convert input or output of schema

The JSON Schema package now supports a new typeMode configuration option, which allows you to specify whether you want to convert the input or output of a Valibot schema.

This is particularly useful when validating and defining an API endpoint with Valibot and your schemas contain transformations. This is because external developers are usually interested in the input schema of the request data but in the output schema of the response data.

import * as v from 'valibot';
import { toJsonSchema } from '@valibot/to-json-schema';

const ValibotSchema = v.pipe(
  v.string(),
  v.decimal(),
  v.transform(Number),
  v.number(),
  v.maxValue(100)
);

toJsonSchema(ValibotSchema, { typeMode: 'input' });

// {
//   $schema: "http://json-schema.org/draft-07/schema#",
//   type: "string",
//   pattern: "^[+-]?(?:\\d*\\.)?\\d+$"
// }

toJsonSchema(ValibotSchema, { typeMode: 'output' });

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

Override default JSON Schema conversion

The JSON Schema package now enables you to override the default behaviour of the JSON Schema conversion process. This can be achieved using the three new configuration options: overrideSchema, overrideAction and overrideRef. These let you specify a custom function that will be called for each schema, action or reference during conversion.

You can either return a value to override the default behaviour, or return null or undefined to skip the override. Furthermore, all three callback functions provide the full context via the first function argument, enabling you to perform the same actions as we do internally.

import * as v from 'valibot';
import { toJsonSchema } from '@valibot/to-json-schema';

const ValibotSchema = v.object({ createdAt: v.date() });

toJsonSchema(ValibotSchema, {
  overrideSchema(context) {
    if (context.valibotSchema.type === 'date') {
      return { type: 'string', format: 'date-time' };
    }
  },
});

// {
//   $schema: "http://json-schema.org/draft-07/schema#",
//   type: "object",
//   properties: {
//     createdAt: { type: "string" format: "date-time" }
//   },
//   required: ["createdAt"]
// }

New global definition storage

If you are reusing Valibot schemas within other Valibot schemas, you may be interested in representing these schemas as references in the JSON Schema output. To facilitate this, the JSON Schema package now includes a new global definition storage.

This feature allows you to define these definitions after creating a Valibot schema, rather than when calling toJsonSchema. This can be particularly useful for larger projects with many schemas, as it helps to keep your code clean and organised.

import * as v from 'valibot';
import { addGlobalDefs, toJsonSchema } from '@valibot/to-json-schema';

const ValibotSchema1 = v.string();
const ValibotSchema2 = v.number();

addGlobalDefs({ ValibotSchema1, ValibotSchema2 });

const ValibotSchema3 = v.tuple([ValibotSchema1, ValibotSchema2]);

toJsonSchema(ValibotSchema3);

// {
//   $schema: "http://json-schema.org/draft-07/schema#",
//   type: "array",
//   items: [
//     { $ref: "#/$defs/ValibotSchema1" },
//     { $ref: "#/$defs/ValibotSchema2" }
//   ],
//   minItems: 2,
//   $defs: {
//     ValibotSchema1: { type: "string" },
//     ValibotSchema2: { type: "number" }
//   }
// }

Output schema definitions only

If you're working with the OpenAPI specification, you might be interested in generating only the JSON Schema definitions and overriding the reference IDs to customise them. The new toJsonSchemaDefs function and overrideRef configuration option now make this possible.

import * as v from 'valibot';
import { toJsonSchemaDefs } from '@valibot/to-json-schema';

const ValibotSchema1 = v.string();
const ValibotSchema2 = v.number();
const ValibotSchema3 = v.tuple([ValibotSchema1, ValibotSchema2]);

toJsonSchemaDefs(
  { ValibotSchema1, ValibotSchema2, ValibotSchema3 },
  { overrideRef: (context) => `#/schemas/${context.referenceId}` }
);

// {
//   ValibotSchema1: { type: "string" },
//   ValibotSchema2: { type: "number" },
//   ValibotSchema3: {
//     type: "array",
//     items: [
//       { $ref: "#/schemas/ValibotSchema1" },
//       { $ref: "#/schemas/ValibotSchema2" }
//     ],
//     minItems: 2
//   }
// }

You can also convert global definitions added via the new addGlobalDefs function. To do this, call getGlobalDefs to retrieve the definitions and pass them as the first argument to toJsonSchemaDefs.

Enhanced metadata support

Previously we only supported the title and description action directly but not the generic metadata action. This was improved so that title, description and examples can now also be specified via the generic metadata action, providing you with more flexibility in defining your schemas.

import * as v from 'valibot';
import { toJsonSchema } from '@valibot/to-json-schema';

const ValibotSchema = v.pipe(
  v.string(),
  v.email(),
  v.metadata({
    title: 'Email Schema',
    description: 'A schema that validates email addresses.',
    examples: ['jane@example.com'],
  })
);

toJsonSchema(ValibotSchema);

// {
//   $schema: "http://json-schema.org/draft-07/schema#",
//   type: "string",
//   format: "email",
//   title: "Email Schema",
//   description: "A schema that validates email addresses.",
//   examples: ["jane@example.com"]
// }

What's next?

Firstly, I would like to thank @Xiot for his detailed feedback on the JSON Schema package and all the new changes. His help in identifying edge cases and advising on the API design was invaluable.

Next, we will probably release the Zod-to-Valibot codemod to help you migrate to Valibot if you are interested. Stay tuned for that!

Edit page

Partners

Thanks to our partners who support the project ideally and financially.

Sponsors

Thanks to our GitHub sponsors who support the project financially.

  • GitHub profile picture of @antfu
  • GitHub profile picture of @Thanaen
  • GitHub profile picture of @ruiaraujo012
  • GitHub profile picture of @hyunbinseo
  • GitHub profile picture of @F0rce
  • GitHub profile picture of @UniquePixels
  • GitHub profile picture of @nickytonline
  • GitHub profile picture of @KubaJastrz
  • GitHub profile picture of @andrewmd5
  • GitHub profile picture of @caegdeveloper
  • GitHub profile picture of @dslatkin