sinclairzx81 / typebox

Json Schema Type Builder with Static Type Resolution for TypeScript
Other
4.65k stars 150 forks source link

How to use `Value.Check` with raw json schemas? #807

Closed dswbx closed 4 months ago

dswbx commented 4 months ago

According to https://github.com/sinclairzx81/typebox/issues/682 it is currently out of scope for typebox to work with raw json schemas, but this request mainly focuses on value-check typebox-generated json schemas (not to get types from it). What would be the best way to do this? I can't use AJV, as cloudflare environment doesn't allow arbitrary code execution.

I want to validate raw json schemas (generated by typebox) using typebox, because I store the output in database and then retrieve it to validate objects. This won't work:

import { Type } from "@sinclair/typebox";
import { Value } from "@sinclair/typebox/value";

console.log(
   Value.Check(
      Type.Unsafe({
         type: "object",
         properties: {
            foo: {
               type: "string",
            },
         },
         required: ["foo"],
      }),
      { foo: "bar" },
   ),
); // throws

Consulting the code for Check I found out that it checks the Kind symbol, so modifying it slightly like the following worked:

import { Type, Kind } from "@sinclair/typebox";
import { Value } from "@sinclair/typebox/value";

console.log(
   Value.Check(
      Type.Unsafe({
         [Kind]: "Object", // added
         type: "object",
         properties: {
            foo: {
               [Kind]: "String", // added
               type: "string",
            },
         },
         required: ["foo"],
      }),
      { foo: "bar" },
   ),
); // works

I am tempted to believe that I could "simply" write a recursive function that adds the [Kind] prop based on type to make this work. Would this work? Or is there a better way to do it? In any case I believe it would be a nice addition since the schema could also be received over the wire (e.g. OpenAPI spec).

sinclairzx81 commented 4 months ago

@dswbx Hi,

Unfortunately, the [Kind] symbol is a requirement to use the TypeBox Check and Compiler functions (as this property is used both for internal runtime optimizations and type composition checks). In saying this, you can still use raw schematics with TypeBox, but you will need to map them into TypeBox types first (as you mentioned)


I am tempted to believe that I could "simply" write a recursive function that adds the [Kind] prop based on type to make this work. Would this work? Or is there a better way to do it?

So, there's actually been some work done recently to provide better support for raw schematics with a prototype FromSchema function prepared at the link below.

https://github.com/sinclairzx81/typebox/blob/master/example/prototypes/from-schema.ts

FromSchema

The FromSchema function will attempt to map raw schematics into TypeBox types if an appropriate transformation exists. This function has a computed return type of TSchema that can be used for type inference and additional type composition. Note that any unrecognized raw schematics will result in TUnknown.

Reference TypeScript Link Here

import { FromSchema } from './prototypes/from-schema' // copy to your project
import { Type, type Static } from '@sinclair/typebox'
import { Value } from '@sinclair/typebox/value'

const T = FromSchema({               // const T: TObject<{
  type: 'object',                    //   foo: TString,
  properties: {                      //   bar: TString,
     foo: { type: 'string' },        //   baz: TString
     bar: { type: 'string' },        // }>
     baz: { type: 'string' },
  },
  required: ['foo', 'bar', 'baz'],
} as const)

const P = Type.Partial(T)            // const P: TObject<{
                                     //  foo: TOptional<TString>,
                                     //  bar: TOptional<TString>,
                                     //  baz: TOptional<TString>,
                                     // }>

console.log(Value.Check(T, { foo: 'hello', bar: 'world' })) // true

So FromSchema is currently the recommended implementation provided by TypeBox, but isn't ready for inclusion in the library yet (but will likely be included when TB drops support for TS 4.0). For now, you can copy and paste the from-schema.ts module into your project and modify the code based on your requirements.

Hope this helps! S

dswbx commented 4 months ago

@sinclairzx81 thank you for your response, this works perfectly fine – exactly what I was looking for!

Out of curiosity, do you have a timeframe in mind for when you'll drop support for TS 4.0?

sinclairzx81 commented 4 months ago

@dswbx Hey, good to hear :)

Out of curiosity, do you have a timeframe in mind for when you'll drop support for TS 4.0?

Nothing formal as of yet. TS 4.9.5 is still pulling around 20% of all TS downloads (currently 9,765,927 weekly) so will want to see that download figure go down a bit more before dropping support in TB.

Note when 4.x support is dropped, I don't anticipate any major changes in TypeBox when that happens. I'm mostly looking to transition over to Const Type Parameters where appropriate which should be a non-breaking change for anyone on TS 5.x. Unfortunately I can use it today without potentially breaking 4.x users :(

With this said, you can use this feature on FromSchema if you update the signature to.

export function FromSchema<const T>(T: T): TFromSchema<T>
//                         ^ add this

As a rough estimate on when 4.x will be dropped tho, probably sometime nearer the end of this year (as a guess) and after I've had some time to upgrade some of my build tooling (which is currently on older versions of esbuild that also doesn't support const generics).

Hope this brings some insight! Will close of this issue for now. Cheers S

dswbx commented 4 months ago

Ah, I see, makes total sense. Also older 4.x versions seem to have many downloads still.. Let's hope that those numbers drop.

Thanks again for your assistance, explanation and creating this awesome library 🎉