sinclairzx81 / typebox

Json Schema Type Builder with Static Type Resolution for TypeScript
Other
4.56k stars 148 forks source link

Support for `definitions`? #882

Closed Jazcash closed 1 month ago

Jazcash commented 1 month ago

I have some shared types that are reused in multiple places throughout my whole schema and I wish to reference them using Ref, however I don't know how to instruct TypeBox to declare my shared types as definitions or $defs in a way that the generated refs work properly. I'm mainly trying to figure this out because I'm using json-schema-to-typescript to generate d.ts files (because I don't want TypeBox src in the output), and that library expects local definitions to be referenced using patterns such as #/definitions/name.

e.g.

const name = Type.String({ $id: "name" });

const person = Type.Object(
    {
        name: Type.Ref(name),
    },
    { $id: "person" }
);

const schema: JSONSchema4 = {
    $defs: {
        name,
    },
    anyOf: [person],
};

const typings = await compile(schema, "Test");

Causes an error because the final schema has "$ref": "name", which json-schema-to-typescript has no idea how to resolve, I think because local refs must start with #, and anything else it tries to resolve externally?

So if I change Type.Ref(name) to Type.Ref("#/$defs/name") it works just fine, but then I lose the nice coupling and have hardcoded ref strings that are awkward to maintain manually, but doing this correctly generates:

export type Name = string;
export interface Person {
    name: Name;
}
export type Test = Person;

Is there a better way of achieving this?

sinclairzx81 commented 1 month ago

@Jazcash Hi,

TypeBox doesn't provide any facilitates for $defs currently (this is something you will need to structure yourself). However cross referencing types with 'string' arguments is generally recommended (as this translates directly to $ref: '...' schematics). However you can do a better job with typing the reference using the following scheme.

import { Type, Static } from '@sinclair/typebox'

const name = Type.String({ $id: "name" })

const person = Type.Object({ 
  // infers as TString. $ref string specific to target location in $defs
  name: Type.Ref<typeof name>('#/definitions/name'), 
}, { $id: 'person' })

const schema = {
    $defs: {
        name
    },
    anyOf: [
       person 
    ],
} as const

type T = Static<typeof person> // type T = { name: 'string' }

Hope this helps S