Code Monkey home page Code Monkey logo

jzod's Introduction

Jzod

Provides a straightforward JSON interface to Zod schema.

Installation

Plain js use:

npm install @miroir-framework/jzod

Typescript use:

npm install @miroir-framework/jzod-ts

Package jzod-ts provides Typescript types for Jzod schemas, and conversion functions from Jzod Schemas to Typescript types.

Release / Migration notes

From version 0.7.0 onwards, "simpleType" specification is deprecated. It means using, instead of:

{
  type: "simpleType",
  definition: "string"
}

the new, simpler definition:

{
  type: "string",
}

The "simpleType" specification format is de-released from version 0.8.0 onwards. The "subDiscriminator" for discriminated unions, which was used internally by Jzod but had no impact on the generated Zod schemas, is also de-released from version 0.8.0.

Principle

Instead of writing Zod schemas:

import { z } from "zod";

const myZodSchema = z.object({a:z.number().optional(), b: z.array(z.string())})

One may write, to get the equivalent result:

import { jzodToZod } from "@miroir-framework/jzod";

const myJzodSchema = {
  type: "object",
  definition: { 
    a: { type: "number", optional: true },
    b: { type:"array", definition: { type: "boolean" } }
  }
}
const myZodSchema = jzodToZod(myJzodSchema);

Which requires to type undeniably more characters, but provides a plain JSON structure that can be serialized, transformed, and reused. Transparency is furthermore gained, as two Jzod schemas can be easily compared with each other, which is not the case of Zod Schemas (up to my knowledge).

Usage

usage

Using the features of Zod, one may obtain, in a single movement:

  • a form validator, usable in any webapp,
  • the corresponding Typescript types, for the Typescript-inclined, (try it, maybe!)
  • a JSON schema, standardized way of describing data structures,
  • plenty of other things, see Zod Ecosystem

Jzod provides a description of the format of Jzod schemas, a converter to Zod schemas, and a converter from Zod Schemas to Jzod Schemas. The schema describing the format of Jzod Schemas is itself a Jzod Schema (bootstrap).

Jzod depends on zod-to-ts for direct Typescript type generation from Jzod Schemas, and also provides a Zod schema code generator.

Here is a simple example of Jzod Schema featuring union and object types, and some simple type validations:

const union: JzodElement = {
  type: "union",
  definition: [
    {
      type: "object",
      definition: { a: { type: "string" } },
      validations:[{ type: "min", parameter: 5 }, { type:"includes", parameter:"#"}] // string must be at least 5 characters long and contain '#'
    },
    {
      type: "object",
      definition: { b: { type: "number", validations:[ { type: "gte", parameter: 0 }, { type:"lte", parameter:"100"} ] } } // comprised between 0 and 100
    },
  ],
};

Caveat

Jzod to Zod conversion has not been optimized yet. It should already fit most usages, but it may become a performance bottleneck in case of trigger-happy, fire-and-forget conversions. Kind advice: once a Jzod schema has been converted to Zod, please hang on to that Zod schema for repeated validations (parse)!

Features

All "primitive" Zod types are supported (literal, enum, number, string, date, see Zod Primitives), with validations (min, max, etc.), and data structures (object, union, array, tuple, record, etc.).

Jzod allows defining and referencing Jzod schemas within Jzod Schemas, which fosters type reuse, enables direct definition of recursive types, and allows one to avoid relying on js-level constant definition / referencing for such purposes, that can be quite cumbersome.

For details about the (yet) unsupported Zod features, see Limitations and Drawbacks.

References and Recursive types

Jzod enables references

const referencedType: JzodElement = {
  type: "schemaReference",
  context: {
    myString: {
      type: "string" 
    },
    myObject: {
      type: "object",
      definition: {
        a: { type: "schemaReference", definition: { relativePath: "myString" } }
      }
    }
  },
  definition: { relativePath: "myObject" },
};
jzodToZod(referencedType).parse( { a: "test" } ); //success

Jzod Schema References are lazy-evaluated, allowing the definition of recursive types:

const recursiveType: JzodElement = {
  type: "schemaReference", 
  context: {
    "myObject": {
      type: "object",
      definition: {
        a: {
          type: "union",
          definition: [
            {
              type: "string",
            },
            {
              type: "schemaReference",
              definition: { relativePath: "myObject"}
            }
          ]
        }
      }
    }
  },
  definition: { relativePath: "myObject" }
};
jzodToZod(recursiveType).parse( { a: { a: { a: "test" } } } ); //success

Typescript Use

Jzod provides Typescript Types for Jzod Schemas. One thus benefits from completion when creating Jzod Schemas, at least on modern IDEs:

Completion

Conversion to Zod

The function

import { jzodToZod } from "jzod"
jzodToZod(schema: JzodElement)

returns a Zod Schema corresponding to a Jzod Schema.

Conversion From Zod

The function

import { zodToJzod } from "jzod"
zodToJzod(schema: ZodTypeAny, identifier:string)

returns a Jzod Schema corresponding to a Zod Schema. The identifier parameter gives the name of the reference to be used in case a z.lazy() occurs in the type.

For example:

const JzodSchema = zodToJzod(z.lazy(()=>z.any()),"test")); // equivalent to JzodSchema = {"type":"schemaReference","definition":{"relativePath":"test"}}

One thus has to ensure that any lazy-referenced Zod schema is available as a context. The most simple solution is to make any internal definition of a lazy call available as a js const, and use that name as context reference.

Conversion to Typescript

In the separate Jzod-ts package, the function

import { jzodToZod } from "jzod-ts"
jzodToTsCode({ type: "string" }, true/*export declaration*/, "testJzodSchema1")

// /* returns: */
// import { ZodType, ZodTypeAny, z } from "zod";
// export type testJzodSchema1 = string;
// export const testJzodSchema1 = z.string();

The function

import { jzodToTsTypeAliasesAndZodText } from "jzod-ts"
jzodToTsTypeAliasesAndZodText(
  element: JzodElement,
  typeName?: string,
): TsTypeAliasesAndZodText

returns the Typescript Type Abstract Syntax Tree (AST) and the textual form of the Zod definition corresponding to:

  • the context of the defined schema (which is non-empty whenever some schema reference defines such a context whithin the definition),
  • the defined schema itself.

Bootstrap

The jzodBootstrapSetSchema constant contains the bootstrapped defintion of Jzod Schemas. It has the property of self-parsing:

import { jzodBootstrapElementSchema, jzodToZod } from "@miroir-framework/jzod";

jzodToZod(jzodBootstrapElementSchema).parse(jzodBootstrapElementSchema); // succeeds!

This constant thus constitutes the full definition and reference for Jzod Schemas, and is accessible at any time (including runtime).

Advantages compared to plain Zod schemas

When using z.lazy in Zod, the Typescript type cannot be inferred from the Zod Schema (which in any other situation can be accomplished using z.infer<typeof schema>). In these cases however, one has to write both the Typescript type and the Zod Schema, revealing some burden.

Jzod references allow to generate both the Typescript type and the Zod Schema from the Jzod Schema. However, a notorious drawback then persists: benefiting from the Typescript type requires some sort of pre-processing / type generation phase, to produce source files containing the wanted TS types. This pre-processing shall be triggered before / upon any build or reloading of the project at hand.

Extensibility

TBC

Support for Generic / Parametered Types

TBC

Flexible Interpretation

TBC

Limitations and Drawbacks

Jzod does not currently check for adequate use of validation contraint parameters with the employed Zod schema type; for example, it is allowed to pass a parameter to the number int constraint, which does not make sense, since this contraint only checks that the given number is an integer. The type of the parameter is not checked, either. Finally, Jzod does not allow yet to pass a custom error message (second parameter) to validators (TBD).

Are not supported yet: Native enums, effects, most object methods (pick, omit, deepPartial, and merge, but extend and partial are supported), other methods (readonly, brand, pipe) and transforms.

jzod's People

Contributors

arnaud-bailly avatar miroir-framework avatar

Watchers

 avatar

jzod's Issues

move bootstrap Zod Schema constants to jzod package

The boostrap Zod Schema constants: jzodEnumAttributeTypesSchema, jzodArraySchema, jzodElementSchema, jzodObjectSchema, etc. are declared in the jzod-ts package. They are not related to TS, and shall be usable in a js-only scenario. They belong in the jzod package.

add carry-on types and manyfold object union types

Add carry-on types, usable (among other cases) with heteronomous unions:

{
  type: "object",
  definition: {
    a: string,
    b: { c: number }
  },
  carryOn: {
    d: boolean
  } 
}

shall result in a TS type (and Zod schema) corresponding to:

{ a: string | { d: boolean }, b: { c: number  |  { d: boolean } }  |  { d: boolean } } 

For unions among object types, many discriminators can be found, implying the existence of manyfold object union types, that are union types of objects, in which there are many attributes used as discriminators. This is not semantically relevant, as discriminated unions do not exist as a prime citizen in TS types, being treated as any other union. This is however pragmatically relevant, guiding the use of such an object union type. The possibility of having an array of discriminators on a union type shall thus be considered in that perspective.

jzod "lazy" type definition is wrong: replace it with a proper schema reference

The lazy jzod type definition is wrong, and incorrectly translated to Zod.

Now it takes a JzodFunction definition, which makes no sense since JzodFunctions allow to define the signature (or interface) of a function. The lazy type should give a reference to a Jzod schema which would be used at Jzod to Zod schema conversion or by the zod.parse() operation. The given name must be then found in the environment of the executed function, or an error message shall be raised.

Allow partial objects

Partial objects are not allowed.

They should be allowed, for example:

const test1: JzodElement = {
          type: "object",
          partial: true,
          definition: {
            a: {
              type: "simpleType",
              definition: "number",
              nullable: true,
            },
            b: {
              type: "simpleType",
              definition: "number",
              validations: [{ type: "gt", parameter: 5 }],
            },
          },
        };

allowing objects such as

{b:6}

or

{a:1}

.

remove warning on Zod union conversions to Jzod

There's a warning:

zodToJzod: Zod discriminated unions are converted to unions in Jzod, which are converted back as plain unions in Zod, not discriminated unions as the original Zod Schema.

It shall be removed, as Zod discriminated unions are correctly converted to Jzod discriminated unions. The problem lies in the conversion of Jzod discriminated unions to Zod "plain" (not discriminated) unions. Jzod discriminated unions are not converted into Zod discriminated unions because Zod discriminated unions can not be harmoniously nested, there is an irregularity in the "result" (is it the created TS type from zod-to-ts or the parse() behavior?). TODO: CREATE AN ISSUE ON ZOD / ZOD-TO-TS DESCRIBING THE PROBLEM).

A warning shall be added during the conversion of Jzod discriminated unions with multiple discriminators (discriminatorType="array") to Zod, since Zod does not support discriminated unions with multiple discriminators.

rename boostrap Zod Schema constants: jzodArraySchema becomes jzodArray, etc.

The boostrap Zod Schema constants: jzodEnumAttributeTypesSchema, jzodArraySchema, jzodElementSchema, jzodObjectSchema, etc. are named after the entry they are defined by in the constant jzodBootstrapElementSchema.

These names, which end in "Schema", are not aligned with the ones obtained using the TS conversion function (jzodToTsCode, or jzodToTsTypeAliasesAndZodText).

We have now, for example: TS type name "JzodArray", Zod schema name "jzodArraySchema".

It should be: TS type name "JzodArray", Zod schema name "jzodArray".

Reduce package size

All source files, including tests, are included in the packages.

All unuseful files shall be removed from the package.

de-release "simpleType" definitions

Specifying "simpleType" shall not be possible anymore:

{
  type: "simpleType",
  definition: "string"
}

It must be replaced by:

{
  type: "string"
}

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.