sinclairzx81 / typebox

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

Support for self-referencing fields of recursive type #761

Closed lithdew closed 6 months ago

lithdew commented 6 months ago

I have this schema:

const Program = T.Recursive(
  (This) =>
    T.Object({
      accounts: T.Record(
        T.String(),
        T.Union([T.String(), T.Object({ ref: T.KeyOf(T.Index(This, ["accounts"])) })])
      ),
    }),
  { $id: "Program" }
);

type TProgram = Static<typeof Program>;

TProgram resolves to:

type TProgram = {
    accounts: {
        [x: string]: string | {
            ref: never;
        };
}

Is there any way to make this schema such that the following instance of this schema is valid?

const TEST = {
  accounts: {
    programId: "5XDdQrpNCD89LtrXDBk5qy4v1BW1zRCPyizTahpxDTcZ",
    testId: { ref: "programId" },
  },
} satisfies TProgram;
sinclairzx81 commented 6 months ago

@lithdew Hi,

Self referential recursive + indexed types are currently not supported by TypeBox.....however you can try implement this type other ways. For example, the following extracts the accounts property out into it's own type, this avoids the need to self reference via Type.Index.

TypeScript Link Here

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

type TAccounts = Static<typeof Accounts>
const Accounts = T.Recursive(This => T.Record(
    T.String(), 
    T.Union([T.String(), T.Object({
      ref: This
    })
  ])), { $id: 'Accounts' })

type TProgram = Static<typeof Program>
const Program = T.Object({
    accounts: Accounts
})

function test(value: TProgram) {
    const account1 = value.accounts['account1']
    if(typeof account1 === 'string') return // ignore

    const account2 = account1.ref['account2']
    if(typeof account2 === 'string') return // ignore

    // ... and so on
}

Hope this helps S

sinclairzx81 commented 6 months ago

@lithdew Hey, might close of this issue as self referential recursive indexing is generally supported (and it may not be supported in future given the way recursive types are constructed). However, you should be able to use the workaround above to represent this type.

Hope this helps. Cheers! S