Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TSchema can't infer TMappedKey #1172

Closed
intaek-h opened this issue Feb 14, 2025 · 2 comments
Closed

TSchema can't infer TMappedKey #1172

intaek-h opened this issue Feb 14, 2025 · 2 comments

Comments

@intaek-h
Copy link

intaek-h commented Feb 14, 2025

I have a typescript code I want to migrate to Typebox.

interface Dictionary {
  A: { count: number };
  B: { type: string };
  C: null;
}

type Base<K extends keyof Dictionary> = {
  category: K;
  data: Dictionary[K];
};

type BaseMapped = {
  [K in keyof Dictionary]: Base<K>;
}

// type MergedBase = Base<"A"> | Base<"B"> | Base<"C">
type MergedBase = BaseMapped[keyof Dictionary]

I tried Typebox workbench and it gave me this code, which is not equal to the original typescript type.

type Dictionary = Static<typeof Dictionary>
const Dictionary = Type.Object({
  A: Type.Object({
    count: Type.Number()
  }),
  B: Type.Object({
    type: Type.String()
  }),
  C: Type.Null()
})

type Base<K extends TSchema> = Static<ReturnType<typeof Base<K>>>
const Base = <K extends TSchema>(K: K) =>
  Type.Object({
    category: K,
    data: Type.Index(Dictionary, K)
  })

// type BaseMapped = {
//    A: {
//        category: "A";
//        data: never;
//    };
//    B: {
//        category: "B";
//        data: never;
//    };
//    C: {
//        category: "C";
//        data: never;
//    };
// }
type BaseMapped = Static<typeof BaseMapped>
const BaseMapped = Type.Mapped(Type.KeyOf(Dictionary), (K) => Base(K)) // suspecting `K: TMappedKey<["A", "B", "C"]>` not being correctly inferred.

type MergedBase = Static<typeof MergedBase>
const MergedBase = Type.Index(BaseMapped, Type.KeyOf(Dictionary))

I've been into this for hours and I can't find the solution..

@sinclairzx81
Copy link
Owner

sinclairzx81 commented Feb 14, 2025

@intaek-h Hi,

This is a general limitation with Mapped. You can resolve by implementing the TS types slightly differently

Workbench Link

interface Dictionary {
  A: { count: number };
  B: { type: string };
  C: null;
}

// Update: Base<...> accepts exterior Category and Data parameters
type Base<Category extends PropertyKey, Data extends unknown> = {
  category: Category;
  data: Data;
}

// Update: Pass Category + Data as parameter
type BaseMapped = {
  [Category in keyof Dictionary]: Base<Category,  Dictionary[Category]>;
}
// type MergedBase = Base<"A"> | Base<"B"> | Base<"C">
type MergedBase = BaseMapped[keyof Dictionary]

Which generates the following

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

type Dictionary = Static<typeof Dictionary>
const Dictionary = Type.Object({
  A: Type.Object({
    count: Type.Number()
  }),
  B: Type.Object({
    type: Type.String()
  }),
  C: Type.Null()
})

type Base<Category extends TSchema, Data extends TSchema> = Static<
  ReturnType<typeof Base<Category, Data>>
>
const Base = <Category extends TSchema, Data extends TSchema>(
  Category: Category,
  Data: Data
) =>
  Type.Object({
    category: Category,
    data: Data
  })

type BaseMapped = Static<typeof BaseMapped>
const BaseMapped = Type.Mapped(Type.KeyOf(Dictionary), (Category) =>
  Base(Category, Type.Index(Dictionary, Category))
)

type MergedBase = Static<typeof MergedBase>
const MergedBase = Type.Index(BaseMapped, Type.KeyOf(Dictionary))

Just be mindful that while the Workbench does its best to map TS types, it may not always generate a direct translation to TypeBox. Achieving 1-1 mapping is an ongoing work in progress, but mostly if you can simplify TS types things generally map easier. Unfortunately, implementing mapped types at runtime / statically requires an exceptional amount of complexity to achieve, there may be limitations here and there.

Hope this helps
S

@intaek-h
Copy link
Author

@sinclairzx81 Thank you very much. I love your work!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants