apigenie-generatorDocs and dataschema zod

schema zod

Generate Zod (v4) schemas, with their TypeScript types, from JSON Schemas.

(experimental, outside the 1.0 compatibility promise) The generated schemas aren’t yet checked against a live API (conformance Level 3), and the options and the output can still change. schema zod says so in its --help and on the first line it prints, on the standard error. See the experimental commands.

Usage

apigenie-generator schema zod [...OPTIONS] INPUT_FILE

Description

The input is a JSON or YAML file:

  • a JSON Schema document (drafts 4, 6, 7, 2019-09 and 2020-12, 2020-12 when it has no $schema): its root schema, named after its title or the file, and its $defs (or definitions) are generated. A root that only holds definitions is left out;
  • an OpenAPI 3.x document: its components.schemas, with the OpenAPI 3.0 nullable keyword and boolean exclusiveMinimum/exclusiveMaximum;
  • a Swagger 2.0 document: its definitions, with the x-nullable extension;
  • an AsyncAPI document: its components.schemas. Schemas in other formats (Avro, …) are skipped.

Other schemas that a $ref points to, e.g. #/$defs/User/properties/address or a schema of another file, are named after their path (UserAddressSchema), so that each is written once.

Every schema becomes a constant and a type:

import * as z from "zod";

export const UserSchema = z.looseObject({
  id: z.uuid(),
  email: z.email(),
  /** Display name */
  name: z.string().min(1).max(80),
  age: z.int().gte(0).lt(150).optional(),
});
export type User = z.infer<typeof UserSchema>;

Schemas are declared after the schemas they use. A schema using itself, or a schema declared later, from an object property gets a getter:

export const CommentSchema = z.strictObject({
  text: z.string(),
  get replies() {
    return z.array(CommentSchema).optional();
  },
});

Elsewhere, e.g. items of an array schema using the array itself, the reference is wrapped in z.lazy() and the constant is typed z.ZodType, so its inferred type is unknown. This is reported as a warning.

How keywords are mapped

JSON Schema Zod
type z.string(), z.number(), z.int() (z.int32() for the int32 format), z.boolean(), z.null(), z.array(), objects; several types make a z.union(), null with other types .nullable()
no type the type implied by the keywords, e.g. minLength makes a string; z.unknown() otherwise
enum, const z.enum() for strings, z.literal() otherwise
format z.email(), z.url() (uri, iri), z.uuid(), z.iso.datetime({ offset: true }), z.iso.date(), z.iso.duration(), z.ipv4(), z.ipv6(), z.hostname(), z.base64() (byte), z.base64url(); other formats are not validated
minLength, maxLength, pattern .min(), .max(), .length(), .regex()
minimum, maximum, exclusiveMinimum, exclusiveMaximum, multipleOf .gte(), .lte(), .gt(), .lt(), .multipleOf()
items, minItems, maxItems z.array(), .min(), .max()
prefixItems (or an array of items) z.tuple(), with optional items beyond minItems and a rest of items (additionalItems)
uniqueItems, contains refinements
properties, required the shape, with .optional() for properties that aren’t required, or .default() with their default
additionalProperties none or true: z.looseObject() (unknown properties are kept); false: z.strictObject(); a schema: .catchall(), or z.record() without properties
patternProperties a refinement validating the matching properties
minProperties, maxProperties, propertyNames, dependentRequired, dependentSchemas (dependencies) refinements
allOf z.intersection()
anyOf, oneOf z.union(), or z.discriminatedUnion() when a required property with constant values tells the objects apart (e.g. the OpenAPI discriminator)
not, if/then/else refinements
$ref the referenced schema’s constant
description (or title), deprecated JSDoc comments; .describe() with the describe option

Not supported, and reported as warnings: unevaluatedProperties and unevaluatedItems. $dynamicRef and $recursiveRef are resolved to their initial targets.

In very large graphs of recursive schemas, TypeScript may give up inferring a type (TS2589: Type instantiation is excessively deep at z.infer); the schemas still validate.

Approximations: oneOf is not exclusive, uniqueItems compares items by their JSON text, string lengths count UTF-16 code units, and uuid accepts the versions known to Zod. The types of properties are implied by their keywords also where JSON Schema accepts other types, e.g. { "minLength": 1 } rejects numbers.

Extensions

Extension Effect
x-apigenie.codegen.name base of the names of the constant and the type of a schema (AnimalSchema, Animal)
x-nullable true makes a schema accept null, as in Swagger 2.0 documents

The extensions can live in an overlay for this target instead of in the document, e.g. zod.overlay.yaml:

overlay: 1.0.0
info:
  title: Zod names
  version: 1.0.0
actions:
  - target: $.components.schemas.Pet
    update:
      x-apigenie:
        codegen:
          name: Animal
apigenie-generator schema zod openapi.yaml --overlay zod.overlay.yaml -o out

Options

-c, –config

Path to the configuration file to use. See the “Configuration” section for more information.

–overlay OVERLAY

Path to an OpenAPI Overlay (1.0 to 1.2) applied to the input before generating, e.g. one setting the extensions of the “Extensions” section. Can be repeated: the overlays apply in order, after those listed under commands.generate.schema_zod.overlay in the configuration file (with paths relative to it). Warnings and errors about a value an overlay set point to the overlay.

–strict

Makes an overlay action whose target selects nothing an error (exit status 2, nothing is generated) instead of a warning (OVERLAY:LINE:COL: action #N (TARGET) selects no nodes.).

-o, –output

Directory where the schemas are generated.

–package-name

Name of the generated package. Defaults to the title of the document or the name of the file.

–package-version

Version of the generated package. Defaults to the version of the API, or 0.0.0.

Configuration

version: '1.0'
commands:
  generate:
    schema_zod:
      layout: package
      import_extension: .js
      schema_suffix: Schema
      type_suffix: ''
      types: true
      additional_properties: loose
      describe: false
Option Description
layout package (default): an npm package with src/schemas.ts and src/index.ts; sources: schemas.ts only
import_extension Extension of relative imports: .js (default, Node.js) or an empty string (bundlers)
schema_suffix Appended to the names of the schema constants (default Schema)
type_suffix Appended to the names of the types (default none)
types Whether export type X = z.infer<typeof XSchema> is generated (default true)
additional_properties Objects without additionalProperties: loose (default) keeps unknown properties, strip removes them (z.object())
describe Whether descriptions are attached with .describe() (default false)