sinclairzx81 / typebox

Json Schema Type Builder with Static Type Resolution for TypeScript
Other
4.77k stars 152 forks source link

Composite working uncorrect #773

Closed RumyantsevOlegRelabs closed 6 months ago

RumyantsevOlegRelabs commented 6 months ago

When I use composite on two schemas, and for example at least in one of the schemas a field is not written with typeBox for example I specify the type of one of the fields as a normal JSON schema created without builder (it is generated automatically in runtime). Then composite stops working. Everything is fine if all fields are described with typebox, in case if at least one field is not, it returns an empty schema.

I think this is not expected behavior

Also maybe you can tell me how to reuse schemas there is a T.Omit and it allows me to delete fields at a high level, but what if I need to delete it at a lower level with nesting? Or what if I want to reuse the base schema and add some fields to it? I thought it was possible to use composite (but the problem is described above).

I also tried to find more documentation but all I found was information on github (Maybe there are some other sources).

RumyantsevOlegRelabs commented 6 months ago

I can't believe that there isn't just an elementary way to add a field (or series of fields to a schema, since it's a simple function). So I think I'm just doing it wrong and I need to ask for advice

sinclairzx81 commented 6 months ago

@RumyantsevOlegRelabs Hi,

Can you post a reproduction of the type you are using Composite with?

Also maybe you can tell me how to reuse schemas there is a T.Omit and it allows me to delete fields at a high level, but what if I need to delete it at a lower level with nesting?

Composite only operates on the direct properties of the Object(s) being composited. If you need deeper property merging, you will need to handle that manually (or write a function to handle it), as this isn't supported by TypeBox's standard API.

If you can post the Composite causing issues, I may be able to provide some assistance.

RumyantsevOlegRelabs commented 6 months ago

Unfortunately I can't publish the schematic as the project is under NDA. But I can describe it in more detail. Compose is used as in the example and in some places it works correctly. But everything stops working if at least one of the fields of at least one of the compose schemes is not described using a typeBox Example: const SomeOtherStyleSchema = { properties: { // It's just an example, don't take it seriously // But I can tell you that this is a valid JSON schema } }

T.Object({ todo: T.String(), age: T.Number(), some: SomeOtherStyleSchema })

sinclairzx81 commented 6 months ago

@RumyantsevOlegRelabs I see,

It's unfortunately not possible to compose (or compute) raw Json Schema with TypeBox types in this way. Internally, TypeBox handles computed types (like Composite) by introspecting a special Kind symbol on each type. This symbol is used quickly identify the structure of the type, and apply the appropriate transformations based on that. Because raw Json Schema does not contain the Kind symbol, it will not compose in the way you expect.

It is possible to augment an existing schema and apply the Kind symbol, but this something you will need to write yourself. The following augments a simple { x, y, z } object with the necessarily symbols (making it compatible with Composite). You can also console.log(schema) to print out the symbols applied to TypeBox generated types as a reference.

TypeScript Link Here

import { Type as T, Kind, TObject, TNumber } from '@sinclair/typebox'

// Note: Raw Json Schema is not immediately compatible with computed type composition as
// computed types operate exclusively on a Kind type descriptor. Additionally, computable
// schematics are limited to a finite set of recognized schematics supported by TypeBox.

const SomeOtherStyleSchema = {
    [Kind]: 'Object',  // <-- required Kind here
    type: 'object',
    required: ['x', 'y', 'z'],
    properties: {
      x: { type: 'number', [Kind]: 'Number' }, // <-- required Kind here
      y: { type: 'number', [Kind]: 'Number' }, // <-- required Kind here
      z: { type: 'number', [Kind]: 'Number' }  // <-- required Kind here
    }
} 

const S = T.Object({
    todo: T.String(),
    age: T.Number(),
    some: SomeOtherStyleSchema as TObject<{ x: TNumber, y: TNumber, z: TNumber }> // <-- requires as TObject 
})

Alternative

Instead of using Composite, you can try Intersect. Note that Composite is a VERY advanced type and requires deep introspection into the types it's being asked to compose, however Intersect does not perform any computation on the type, but comes at a cost of expressing the schema with allOf. Unlike Composite, Intersect does not flatten the schematics into a single output object schematic, but does produce a schematic that is functionally the same thing (just try replacing Composite for Intersect and see how you go)

Hope this helps S

sinclairzx81 commented 6 months ago

@RumyantsevOlegRelabs Hiya,

Might close off this issue as computing types with raw Json Schema is generally not supported. I would recommend trying Intersect over Composite however in your case (as this should compose without computing the flatten schema), however be mindful that you should be using Ajv to validate with raw Json Schema (as with computing types for composition, TypeBox bases it's validation logic of the Kind symbols as well)

All the best. S