Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
137 views
in Technique[技术] by (71.8m points)

javascript - How to create a generic TypeScript interface which matches any type/interface/object with restrictions on types for values inside it?

I want to create a generic TypeScript interface which matches any type or interface or object with restrictions on types for values inside it.

Here's MyInterface which has properties fooIProp and barIProp which stores strings with an example:

interface MyInterface {
  fooIProp: string;
  barIProp: string;
};

const testInterface: MyInterface = {
  fooIProp: "foo",
  barIProp:  "bar"
};

Here's MyType type alias which has properties fooTProp and barTProp which stores strings with an example:

type MyType = {
  fooTProp: string;
  barTProp: string;
}

const testType: MyType = {
  fooTProp: "foo",
  barTProp: "bar"
}

Here's an object which has properties fooObjectKey and barObjectKey which stores strings:

const testObject = {
  fooObjectKey: "foo",
  barObjectKey: "bar"
}

I create MyGenericInterface which accepts object with strings as keys and values as follows:

interface MyGenericInterface { [key: string]: string }

Then I try to assign testInterface to MyGenericInterface

const testFromInterface: MyGenericInterface = testInterface;
const testFromType: MyGenericInterface = testType;
const testFromObject: MyGenericInterface = testObject;

It throws TS2322 error:

Type 'MyInterface' is not assignable to type 'MyGenericInterface'.
  Index signature is missing in type 'MyInterface'.(2322)

Here's TypeScript Playground for reference.

Question: How can I create a generic TypeScript interface which matches any type/interface/object with restrictions on types for values inside it?

question from:https://stackoverflow.com/questions/65832224/how-to-create-a-generic-typescript-interface-which-matches-any-type-interface-ob

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

It is a known issue (and currently working as intended) that implicit index signatures are not added to values of an interface type, whereas they are added for anonymous object types. See microsoft/TypeScript#15300 for discussion.


Technically speaking it is not safe to add implicit index signatures this way. Object types in TypeScript are open/extendible and not closed/exact (see microsoft/TypeScript#12936 for discussion/request for exact types), so it's always possible to have properties in addition to the known properties mentioned in the interface:

const someObject = {
  fooIProp: "foo",
  barIProp: "bar",
  baz: 12345
};
const unexpectedMyInterface: MyInterface = someObject; // no error

(although if you try to add such unknown properties directly in an annotated object literal you will get excess property warnings, which sometimes makes object types in TypeScript seem exact when they are not).

The fact that unexpectedMyInterface is assignable to MyInterface but not to MyGenericInterface is why it is technically correct to prohibit the assignment. Of course you can go through the same exercise with MyType:

const someOtherObject = {
  fooTProp: "foo",
  barTProp: "bar",
  baz: 12345
};
const unexpectedMyType: MyType = someOtherObject;

...yet the bad assignment of unexpectedMyType to MyGenericInterface is allowed:

const badMyGenericInterface: MyGenericInterface = unexpectedMyType;
console.log(badMyGenericInterface.baz?.toUpperCase()); // no error in TS, but 
// RUNTIME ?? badMyGenericInterface.baz.toUpperCase is not a function

So, what gives? It seems to be a tradeoff. It is considered "safer" to allow this for anonymous object types and not interface types because interface types can be augmented or merged later, while anonymous object types can't be.

Personally I don't see it as much safer for one than the other. If you want to see implicit index signatures apply to interface types also, then you might want to go to microsoft/TypeScript#15300 and give it a ??.


Until and unless that feature gets implemented, all you can have is workarounds. One is to use a mapped type to convert a value of MyInterface to an object type equivalent before assigning it to MyGenericInterface:

type Id<T> = { [K in keyof T]: T[K] } // change interface to mapped type version
type MyInterfaceAsType = Id<MyInterface>;
const testFromInterface2: MyGenericInterface = testInterface as MyInterfaceAsType
const testInterfaceAnonymousTypeVersion: MyInterfaceAsType = testInterface;
const testFromInterface3: MyGenericInterface = testInterfaceAnonymousTypeVersion;

There may be other workarounds but I'd need to see more of a use case before spending too much time suggesting them. Where are you seeing failures you'd like to fix?


Playground link to code


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...