TypeScript type with specific set of keys and values

Lukas

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 BodyNode which 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
Rich N

I don't think you can do this with interfaces: the 'extends' always means you can extend. However how about what's below? It's a little clunky since we're defining our new type inside <>, but I think it does what you want.

type AllowedKeys = "a" | "b" | "c";

type MyGenericType<T extends AllowedKeys> = {
    type: T;
}

type MyObjectTypeGuard = { [T in AllowedKeys]: MyGenericType<T> };

type VerifyType<T extends MyObjectTypeGuard &
    { [U in Exclude<keyof T, AllowedKeys>]: never }> = T;

// Incorrect type letters or missing 'type', additional properties,
// and missing properties lead to errors
type VerifiedType = VerifyType<{
    a: { type: "a" } & { foo: "foo" };
    b: { type: "b" } & { bar: "bar" };
    c: { type: "c" } & { baz: "baz" };
}>;

const obj: VerifiedType = {
    a: { type: "a", foo: "foo" },
    b: { type: "b", bar: "bar" },
    c: { type: "c", baz: "baz" }
}

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

The type to indicate keys that be set specific type value in Typescript

Typescript object with keys of specific type (and working intellisense)

TypeScript provide default values for missing keys of a Type

Typescript: Use object values as keys for a new type

Typescript Record type with string keys infers invalid type for it's values

Pulling keys out of typescript type/interface where the values are of type T

What is the Rust equivalent of a TypeScript hashmap with optional typed values for specific keys?

Typescript - How to get keys behind specific values from an object

Get all keys of specific type from object in typescript

How to declare a "Record" type with partial of specific keys in typescript?

Pick Values from JSON Object Where Keys in Typescript Type

typescript type for object with unknown keys, but only numeric values?

Typescript: How to declare a type of array whose values are equal to keys of object?

Typescript: Generate Type from Object keys and Array string values

Typescript type for two types with same keys and similar values

How to modify a specific set of values in a nested dictionary with multiple keys in Python?

Extract set of values from collection of maps by specific keys

Typescript: Map with keys being the same type as values without restricting type of keys

TypeScript object type with keys from other object type where the value of the key matches a specific type

How to dynamically set a value of a specific type using generics in Typescript

Is it possible to have mixed specific typed keys in a TypeScript type, and a generic key type?

Typescript map specific keys in object

set type for the key values of Object.entries in typescript

TypeScript: Type to contain any value EXCEPT values from a predefined set

TypeScript: How can I extract from a type only the keys and values of type array

TypeScript: How to create an interface for an object with many keys of the same type and values of the same type?

Typescript: Type alias results in strange type when returning object with same keys but different values

How to set a type to be a string but not allowing some specific values?

Type hint for a function that returns only a specific set of values