Updated Version
Full Playground here
https://www.typescriptlang.org/play?#code/C4TwDgpgB...
I am trying to create an object type that only allows specific keys, but for each key you must extend a specific generic type as value. I.e.
type AllowedKeys = "a" | "b" | "c";
type MyGenericType<T extends AllowedKeys> = {
type: T;
}
type MyObjectTypeGuard = {
[T in AllowedKeys]: MyGenericType<T>
}
// This is what I am aiming at, I tried to make sure that I now have an interface the should prevent me from using the only allowed keys with the allowed values
interface MyObject extends MyObjectTypeGuard {
a: { type: "a" } & { foo: "foo" };
b: { type: "b" } & { bar: "bar" };
c: { type: "c" } & { baz: "baz" };
}
Now that prevents me from using keys with the wrong values
// I cannot accidentially use the wrong base types
interface MyObject2 extends MyObjectTypeGuard {
a: { type: "b" } & { foo: "foo" };
b: { type: "b" } & { bar: "bar" };
c: { type: "c" } & { baz: "baz" };
}
// Or use completely wrong types even
interface MyObject3 extends MyObjectTypeGuard {
a: { foo: "foo" };
b: { type: "b" } & { bar: "bar" };
c: { type: "c" } & { baz: "baz" };
}
But I can still do other things that I want myself to prevent from
// But I can still miss a key
interface MyObject4 extends MyObjectTypeGuard {
a: { type: "a" } & { foo: "foo" };
b: { type: "b" } & { bar: "bar" };
}
// And I can still add arbitrary stuff
interface MyObject5 extends MyObjectTypeGuard {
a: { type: "a" } & { foo: "foo" };
b: { type: "b" } & { bar: "bar" };
c: { type: "c" } & { baz: "baz" };
fooBar: "baz";
}
I am looking for a way to define an object type / interface that forces me to use all keys (and only those) and has me extend a specific generic type for each value
Old version
We have a base model
BodyNodewhich can be extended into variants:type BodyNode<T extends NodeType> = { readonly type: T; }; type NodeWithText<T extends NodeType> = BodyNode<T> & WithText; type NodeWithChildren<T extends NodeType> = BodyNode<T> & WithChildren; type NodeWithReference<T extends NodeType> = BodyNode<T> & WithReference; interface WithText { readonly text: string; } interface WithChildren { readonly children: Node[]; } interface WithReference { readonly href: string; } ``` to be able to iterate over the node types, we created an interface that maps `NodeType`s to variants types: ```typescript type BodyNodes = { [T in NodeType]: BodyNode<T>; }; interface Nodes extends BodyNodes { headline: NodeWithText; paragraph: NodeWithChildren; anchor: NodeWithReference; } type Node = Nodes[keyof Nodes]; ``` implementation: ```tsx type Components = { [K in BodyNode]: React.FC<{ node: Nodes[K] }>; }; const components: Components = { headline: Headline, paragraph: Paragraph, anchor: Anchor, }; const Body = (props: { nodes: Node[] }) => ( <div> {props.nodes.map(node => { const Component = components[node.type]; return <Component node={node} />; })} </div> ); ``` we are using the node types as keys for the `Nodes` as well as the `Components` and with this we can map the Nodes with the right types to the right components in type safe way. But this implementation has a big flaw: ```typescript type BodyNodes = { [T in NodeType]: BodyNode<T>; }; interface Nodes extends BodyNodes { headline: BodyNode<'headline'>; paragraph: BodyNode<'headline'>; // ^^^^^^^^ this will cause an error, which is what we want anchor: BodyNode<'anchor'>; yadda: 'foobar'; //^^^^^^^^^^^^^^^^ this is still possible because we can extend BodyNodes in // any way and that's not cool } ``` We'd love to find a way to write a type that allows only - specific keys as above - for specific keys only specific values as above - requires each key to be there - BUT forbids extending, ie. have an exact implementaition of that type and nothing else
Nodesdoes not compile because it mentionsNodeWithText,NodeWithChildren, andNodeWithReferencewhich are generic types that need type parameters. Could you try to make sure your code constitutes a minimal reproducible example suitable for dropping into a standalone IDE like The TypeScript Playground? The minimum example probably has less code also: if this is what you're looking for, for example