TypeScript: Index signature is missing in type (only on interfaces, not on type alias)

TypeScript Version: 2.2.2

Code

interface IndexType {
     [key: string]: string;
}

interface doesNotWork {
    hola: string;
}
type doWorks = { hola: string };

let y: IndexType;

const correctA = { hola: "hello" };
const correctB: doWorks = { hola: "hello" };
//error should be assignable to y
const error: doesNotWork = { hola: "hello " };

y = correctA;
y = correctB;
y = error; //Index signature is missing in type 'doesNotWork'
y = {... error}; //workaround but not equivalent since the instance is not the same

Expected behavior: The code should not result on a compiler error since the interface doesNotWork is equivalent to the type { hola: string }

Actual behavior: Variable error of type doesNotWork can’t be assigned to y

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 370
  • Comments: 91 (14 by maintainers)

Commits related to this issue

Most upvoted comments

I was working on a somewhat related example today and I was pretty confused by the results. I tried to create some related situations so I could understand the rules.

As I understand:

  1. A specific type can be saved into a more generic type.
  2. A specific interface cannot be saved into a more generic interface. (unexpected)
  3. A specific interface can extend an more generic interface.
  4. A specific type can be saved into a more generic interface.
  5. A specific interface cannot be saved into a more generic type.

I tested this in typescript playground:

// First case (expected)
type A = {
    [x: string]: number
};
type B = {
    x: number
};
const b: B = { x: 1 };
const a: A = b; // no error


// Second case (unexpected)
interface C {
    [x:string]: number
}
interface D {
    x: number
}
const d: D = { x: 1 };
const c: C = d; // error
// Type 'D' is not assignable to type 'C'.
//   Index signature is missing in type 'D'.


// Third case (expected)
interface E {
    [x: string]: number
}
interface F extends E {
    x: number
}
const f: F = { x: 1 };
const e: E = f; // no error


// Fourth case (expected)
interface G {
    [x: string]: number
}
type H = {
    x: number
};
const h: H = { x: 1 };
const g: G = h; // no error


// Fifth case (maybe expected?)
type I = {
    [x: string]: number
}
interface J {
    x: number
}
const j: J = { x: 1 };
const i: I = j; // error
// Type 'J' is not assignable to type 'I'.
//   Index signature is missing in type 'J'.

Just to fill people in, this behavior is currently by design. Because interfaces can be augmented by additional declarations but type aliases can’t, it’s “safer” (heavy quotes on that one) to infer an implicit index signature for type aliases than for interfaces. But we’ll consider doing it for interfaces as well if that seems to make sense

This limitation also makes it practically infeasible to use a custom JSON type as a generic constraint for HTTP request libraries. Context: https://github.com/sindresorhus/ky/pull/80#issuecomment-460216040

I have an example where this rule is inconsistent when casting to the expected indexed type:

interface MyInterface {
  a: number
  b: number
}

const doesNotWork: { [key: string]: number } = {} as MyInterface
// Index signature is missing in type 'MyInterface'.

const doesWork: { [key: string]: number } = {} as { a: number, b: number }

Playground link

What is it that is considered different about these that is preventing the first case? Note that this is occurring in 2.7 (see the playground link)

Why is this still not fixed?

I do think some change is certainly worthwhile, as this issue seems to extend to Records. For example, ESLint recommends using Record<string, unknown> instead of object as a type. However, interfaces by default are considered incompatible with Record<string, unknown>, despite the fact that everything is supposed to be assignable to unknown.

Though not quite what I wanted to hear, I think steve-taylor’s argumet earlier is valid. But if your object is expected to take virtually anything as a property’s value due to the unknown, it doesn’t make sense for interfaces to be incompatible. (Extension is a workaround, but it doesn’t really make sense here.)

Interestingly enough, interfaces are assignable to Record<string, any>.

@steve-taylor any thoughts on this particular scenario? I’d think making interfaces assignable to Record<string, unknown> wouldn’t break OOP.

Ran into this when trying to type a function argument that can have arbitrary keys but a known set of values. Unfortunately, the variable I was trying to pass into the function is generated from graphql-code-generator, so requires a bit more legwork than just changing an interface to a type manually. A quick workaround is to spread the variables before passing it in.

interface X {
    search?: string | null
}
const x: X = { search: "hello" }

function y(z: { [key: string]: string | string[] | null | undefined }) {
    console.log(y);
}

y(x) // Error
y({ ...x }) // Fine

Playground

Just to fill people in, this behavior is currently by design. Because interfaces can be augmented by additional declarations but type aliases can’t, it’s “safer” (heavy quotes on that one) to infer an implicit index signature for type aliases than for interfaces. But we’ll consider doing it for interfaces as well if that seems to make sense

@RyanCavanaugh it wouldn’t make sense without also breaking polymorhism, which is a cornerstone of OOP. All sub-interfaces should be type compatible with their common base interface to enable polymorhism. Although an interface may be type compatible with an indexed interface by way of inference, it doesn’t necessarily mean that its sub-interfaces are type-compatible, because they may introduce additional properties of any type.

@RafaelSalguero consider the following interface:

interface ThisIsWhyItDoesNotWork extends doesNotWork {
    meaningOfLife: number;
}

It’s a well-established OOP convention that expressions of type ThisIsWhyItDoesNotWork can be assigned where expressions of type doesNotWork can be assigned. Although doesNotWork is compatible with the index signature of IndexType, ThisIsWhyItDoesNotWork isn’t. TypeScript can’t determine at compile-time whether expressions of type doesNotWork are instances of doesNotWork or ThisIsWhyItDoesNotWork, so it can’t allow doesNotWork expressions to be assigned where IndexType is expected.

You can fix this in one of three ways:

  1. Use doWorks instead of doesNotWork
  2. Introduce the same constraint into doesNotWork, allowing TypeScript to make the desired inference:
    interface doesNotWork {
        [key: string]: string;
        hola: string;
    }
    
  3. Extend IndexType:
    interface doesNotWork extends IndexType {
        hola: string;
    }
    

I vote to close this issue.

我已收到你的邮件

Something that can help considerably with this issue is to use the following type:

export type Typify<T> = { [ K in keyof T ]: T[ K ] };

Now, if you encounter an issue similar to the one below, you will be able to get out of troubles:

type O1<T = any> = {
    [ K: string ]: T;
};

interface O2<T = any> {
    [ K: string ]: T;
}

interface A {
    a: number;
}


const a: A = { a: 1 };

const o1: O1 = a; // working
const o2: O2 = a; // working

const f1 = <T extends {}>(arg: O1<T>): T => undefined;
const f2 = <T extends {}>(arg: O2<T>): T => undefined;

const ret1 = f1(a); // Argument of type 'A' is not assignable to parameter of type 'O1<{}>'. Index signature is missing in type 'A'.ts(2345)
const ret2 = f2(a); // Argument of type 'A' is not assignable to parameter of type 'O1<{}>'. Index signature is missing in type 'A'.ts(2345)

const a2: Typify<A> = { a: 1 };

const ret21 = f1(a2); // working!!
const ret22 = f2(a2); // working!!

This limitation makes it impossible to provide type definitions for a method that accepts anything JSON-serializable without bugging users to use workarounds:

type JSONValue =
  | string
  | number
  | boolean
  | null
  | JSONValue[]
  | { [property: string]: JSONValue };

declare function takesJSON(arg: JSONValue): void;

// ^-- library code
// =======================
// v-- user code

interface SpecialValue {
  foo: {
    property: number;
  };
}

declare const arg: SpecialValue;
takesJSON(arg)
//        ^-- error here, but should work

https://www.typescriptlang.org/play?#code/C4TwDgpgBAUgygeQHIDUCGAbArtAvAKCigB8oBnYAJwEsA7Ac0JKlqwFsAjCSp0jge34YIaWrxZYMGcfGTpsEANoBdcQG8oisJX6RKoAFzkqdesqOzUmHFAC+Abnz4AJhADGGNJWgAzLLTdgan5aKGA0AGsIMksACi96C0QrBQBKIwA3fmpnR3wAenyoAD0AWlKoDGoOSi8QKDd+VwKi3Db2js6utpaoDPKoLDJuBqaIJzpgbh80N2g4SDdqTHkbNSYfQSN1oiJtXW5DCU5uRyIHfFsnVw8vaEbaCigEowX3ZYxViEdwqJjk+KUeipXq7MFlCrcHSUKAAC24EAANFAOFhgORYfxJM4oAB3fiUCJAA

my workaround for an error Type ‘ISomeInterface’ does not satisfy the constraint ‘Record<string, unknown>’. Index signature is missing in type ‘ISomeInterface’.(2344)

I used Omit utility type Omit<ISomeInterface, never>

What about this utility type?

export type IndexSignature<O extends object> = {
  [P in keyof O]: O[P];
};

interface IFooBar {
    foo: string;
    bar: number;
}

declare const foobar1: IFooBar
declare const foobar2: IndexSignature<IFooBar>

declare function fn(obj: Record<string, unknown>): void

fn(foobar1); // Error
fn(foobar2) // Work

Playground Link

I published this utility type in ts-indexify package.

You can extend types through intersection types. Is there anything I miss here?

// Interface
interface A { x: number; }
interface B { y: string;}

interface C extends A, B { b: boolean;}

var x: C = { b: true, x: 1, y: "" };


// Type
type TA = { x: number; };
type TB = { y: string; };

type TC = TA & TB & { b: boolean; };

var tx: TC = { b: true, x: 1, y: "" };

Forever ago, @RyanCavanaugh said,

Because interfaces can be augmented by additional declarations but type aliases can’t, it’s “safer” (heavy quotes on that one) to infer an implicit index signature for type aliases than for interfaces.

Could someone walk me through why declaration merging matters again? Imagine a world in which there were no errors in the following code:

interface Foo {
  a: string;
  b: string;
  c: string;
}
function yellProps(x: { [k: string]: string }) {
  Object.keys(x).forEach(k => console.log(x[k].toUpperCase() + "!!!"));
}
function bar(foo: Foo) {
  yellProps(foo); // currently an error, but imagine if it were not
  yellProps({ ...foo }); // currently okay
}

Now someone comes along and merges something unexpected:

interface Foo {
  d: number;
}

Wouldn’t this just produce errors inside bar(), as expected?

function bar(foo: Foo) {
  yellProps(foo); // now it should be an error
  yellProps({ ...foo }); // currently error
}

Isn’t that… good? I mean, people who want this feature presumably would be happy with new errors appearing if merges happen. Right now every attempted use of a non-index-signature-having interface in a place that needs an index signature is an error. I’d think the removal of these errors would be worth the potential of some of them coming back due to merging.

So I’m obviously not seeing the argument that declaration merging matters… could someone spell it out for me?

Gotta change dozens of typings from interface to type because of this 😦

Most people here seem to want to assign types like { id: number } to { [key: string]: number }. But, if I am not mistaken, a value of type { id: number } just isn’t a value of type { [key: string]: number }. The second type demands a value for each key, but a { id: number } only provides a value for the particular key id.

It seems to me that the proper solution to this is to introduce a new kind of index type; call it FiniteRecord. An object of type FiniteRecord<string, number> would have the constraint that all string keys that do exist on the object are associated with number values. However, no guarantee would be made that any keys actually exist.

Then, for instance, { id: 15 } would be assignable to FiniteRecord<string, number>.

FWIW, the type FiniteRecord<K, V> is pretty similar to Partial<Record<K, V>>. However, partial records don’t solve the problem being discussed in this thread 😢

How can I define a function that should receive an object where all the properties are string types?

I thought I could do:

type ObjectWithStrings = { [key: string]: string }
declare const requireObjectStrings: <T extends ObjectWithStrings>(object: T) => void;

However due to this issue, this won’t work if the value I happen to pass in uses an interface type:

interface MyInterface {
    foo: string;
}

declare const myInterface: MyInterface;

// Error: Index signature is missing in type 'MyInterface'.
requireObjectStrings(myInterface)

It’s easy to say that I should change the type definition from interface to type, but if this comes from a third party, there’s not always the option to change the type definition. So if this issue is indeed intended behaviour, I presume there is a better and more correct way to define requireObjectStrings so I don’t run into this limitation?

我已收到你的邮件

This issue is old enough to be in kindergarten by now. Has no consensus been reached or has it just been abandoned?

That’s an issue for us as well

This is becoming problematic. A recent change in slonik made this issue resurface. It’s preventing ergonomic transitions to a more typesafe approach without breaking changes.

@acomagu From my understanding your argument is not so much about the specific behavior being discussed in this issue but against type widening, that is, allowing casts of concrete types into more generic ones. It is well understood that type widening worsens type safety but, nonetheless, typescript generally allows it, as it can be seen in the following code:

const concrete: 42 = 42;
const aBitMoreGeneric: 42 | 0 = concrete
const evenMoreGeneric: number = aBitMoreGeneric
const extremelyGeneric: any = evenMoreGeneric

So, if this kind of type widening, which removes all type safety, is allowed, why is casting a { x: string } interface to { [i: string]: string } not allowed (even though casting it to any is allowed)? This leads us to the real problem that is caused by this behaviour: lack of consistency, a problem that manifests itself in three different fronts:

  • Why is type widening generally allowed in typescript but not in this specific case?
  • Why is type widening possible on types declared using type but not on those declared using interface?
  • If this behaviour is disallowed, why not provide an error message saying so (for example the typical one that says that two types are incompatible and casting one to the other requires an intermediate cast to unknown) insteadof the cryptic Index signature is missing in type?

All in all, you could make the design decision that type widening is bad, ban it, and then apply that consistently, banning all direct casts to any, or you could go and say that this case is an exception and then disallow type widening for both interface and type-declared types in order to make this more consistent, or you could even keep the current behavior and allow widening type but not interface, but replace the error message with one that is clearer. Whatever you decide to do, it seems better than the status quo.

I also encounter this pain. It is typically located at the boundary of the code: when sending data through API calls that just require plain JSON hashes (think of a GraphQL variables argument) but this argument is typed for soundness checking in the Typescript code.

For now, I resort to the { ...x } trick of https://github.com/microsoft/TypeScript/issues/15300#issuecomment-436793742 but it looks hacky. It would be great to have an idiomatic way of handling this use case – especially because it encourages the good practice of typing more precisely.

EDIT: Well, actually x ? { ...x } : undefined otherwise x === null transforms that to {}. And now you have to handle null !== undefined 😦

EDIT 2: For now, resorted to using types everywhere, and simulating interface extends with & operator.

I believe I have created a solution to this problem, which seems to work well.

Currently, I am working on a typescript library, which needs to take in some data (unknown shape), JSON.stringify it and send it on its way. My problem was that when you pass a Date object to JSON.stringify, it will create a string representation of that date. But when you parse the string back, it will keep it as a string. This also applies to instances of classes and similar things.

I wanted users using my library to see an error when they pass an object with date property or something not JSON serializable. I googled a bit and found JSON type for typescript that works well. But I am running into this index signature is missing... error too. I cannot force users of my library to not use interfaces, or to wrap everything they pass in with the DeepIndexSignature generic as mentioned above.

My solution is straight forward, although I am not sure if it will work for every use case.

/**
 * JSON serializable object.
 */
export type Json = {
  [key: string]: string | number | boolean | Json | JsonArray;
};

/**
 * JSON serializable array.
 */
export type JsonArray = Array<string | number | boolean | Json | JsonArray>;

/**
 * Creates a new type, that mimics O exactly,
 * except replaces every unsupported value type by union of supported ones.
 */
type RestrictedObject<O, AllowedTypes> = {
  [P in keyof O]: O[P] extends AllowedTypes
    ? O[P]
    : Record<string, never> extends AllowedTypes
    ? O[P] extends Record<string, unknown> ? RestrictedObject<O[P], AllowedTypes>
    : AllowedTypes : AllowedTypes;
};

/**
 * Create wrapper for restricting objects by the Json type
 */
type RestrictedJsonObject<T> = RestrictedObject<T, Json[keyof Json]>

/**
 * Function that needs only JSON serializable objects
 */
function serializeData<T>(data: RestrictedJsonObject<T>): void {
    console.log(data);
}

interface CorrectInterface {
    a: string;
    b: number;
    c: {
        d: string;
        e: number;
    };
}

interface WrongInterface {
    a: string;
    b: string;
    c: {
        d: string;
        e: Date;
    };
}

const value1 = {} as CorrectInterface;
const value2 = {} as WrongInterface;

serializeData(value1);
serializeData(value2); // Throws TS error

You can see that I am able to pass in any object that matches the index signature, and it doesn’t throw an error. When I try to pass in the wrong object, it will throw an error. The last line of the error is index signature, but the line before states that Type 'Date' is not assignable to type 'Json' which I think will have to be enough. It is not the best developer experience, but it does its job.

Does anyone knows a hack from the library “perspective”?

Simple example, I want to create something like that:

interface Entry {
    [key: string]: unknown;
    title: string;
}

const addEntry = (entry: Entry) => {};

So it’s allowing any shape object to be stored somewhere, the only requirement is for the entry is to have a string title.

Now users of my library may want to store books there:


interface Book {
    title: string;
    author: string;
    price: number;
}

const mybook: Book = {
    title: "Mr. Green",
    author: "John Doe",
    price: 55,
};

addEntry(mybook); // FAIL: TS2345: Argument of type 'Book' is not assignable to parameter of type 'Entry'.   Index signature is missing in type 'Book'.

I don’t want my users to be forced to use Typify or IndexSignatureHack types on their types. I’m seeking for a solution that will prevent this error and keep the library types working as intended.

I don’t want my users to extend Entry interface as well, because from their perspective Book type should not accept any other fields that those three defined.

Thanks @patrickneugebauer - I had noticed those rules too, and have resorted to using type over interface in the common case, to allow building layers of type interactions without hitting walls of errors like these.

In my example, changing the interface to a type does indeed get around the error.

There are some other differences between type aliases and interface that might affect your choice - namely you can extend an interface but not a type alias. So it’s not quite that simple, but as rules of thumb go it’s not a bad one.

Here’s how we get around this:

export type IndexSignatureHack<T> = Record<string, unknown> extends T ? { [K in keyof T]: IndexSignatureHack<T[K]> } : T

https://www.typescriptlang.org/play?ssl=1&ssc=1&pln=12&pc=1#code/JYWwDg9gTgLgBAbzgYQMqoApQmAprYXAZzgF84AzbEOAcilwEMBjGWgWACgvcAPSWHBgBPPHACSAOwAmfVMADmkxjACuDABIsA1gB4AKgD44AXkRwA2gGk4wSXG25hECnH0BdAFwSZcxcrVNHQNrd2NSLi4KVUlWYAh7CkkACggAIwArbyQLR2FvIhgoOwUvOBjtSQgAd3tSAEpvADcIYGlECO5OZgTCuELhABtcbylZXnklFXVcLWY9NExsPAJiYzMELjhKBJgAdVxFAAsYbwBWAAYLgBouTqiUgeH6riA

You can verify that there is still type safety by changing the font weight from 500 to '500', for example.

@acomagu I was referring to the mathematical definition of a transitive property, which, when applied to type casting would look like this:

Casting is transitive if, given three types A, B and C such that A can be cast to B and B can be cast to C, A can be cast to C.

I can see how my previous explanation can be confusing, but what I was trying to convey is that, when talking about casting in general (without specifying implicit or explicit casting) there’s already some cases in Typescript where that property is broken.

In any case, that’s an argument concerning the discussion on whether casting from { x: string } to { [i: string]: string } should be allowed, whether that be through explicit or implicit casting (currently it’s impossible through either). Personally, I’m not sure what’s the best course of action here, as the problems that you’ve pointed out are very real but I can also see a need for this type of casts, so I don’t think I can add much to the discussion. What I feel more strongly about is the inconsistency of the current state, which we seem to agree about.

@corollari The type 42 | 0 is a generic type of 42, but { [i: string]: string } is not generic type of { x: string }, I think. “A is a generic type of B” means “The all values acceptable for B can be also acceptable for A”, right? But regarding { [i: string]: string } and { x: string }, there is counterexample, { x: 'str', y: 3 }.

{ x: 'str', y: 3 } is acceptable for { x: string } but not { [i: string]: string }. Because the former type DON’T defines the properties other than x but the latter type demands the all properties should be string. So { [i: string]: string } is not a generic type of { x: string }.

That’s why the casting between these types is different than 42 | 0 and 42, or number. It’s not widening.

Why is type widening possible on types declared using type but not on those declared using interface?

I’m not sure but I guess because interface can cause more complex problem like described at the my previous post, around extends.

If this behaviour is disallowed, why not provide an error message saying so (for example the typical one that says that two types are incompatible and casting one to the other requires an intermediate cast to unknown) insteadof the cryptic Index signature is missing in type?

Agree. More meaningful error message can be helpful for all.

Why is this not fixed yet? I have interfaces auto-generated by a third party library. Now how do I replace them with types, as some clever people here advise?

Seems related, currently there’s no way to enforce that object should have attribute values of specific type only.

Example: I want to define an abstract Table Row, basically any object limited to attribute values as primitive types. But it doesn’t work

// Table row
export type Row = Record<string, string | number | undefined>

// User
interface User { name: string }
const jim: User = { name: 'Jim' }
const row: Row = jim // <== Error

@lgenzelis, ah yes, I adapted the utility type to be more robust by adding the conditional but forgot to update the playground. Without the conditional, it would fail, for example, passing it to React. See here. In that example, if you use the non-ternary version, then there will be type errors.

As for the infinite recursion, it seems TypeScript stops the recursion when it hits a primitive type (e.g., number, string, etc.)

I’m seeing a lot of discussion that some type assignment is expected to display an error but I’m missing actual code that illustrates that issue. In my mind

interface CSSProps { fontWeight: number }

export type Props = CSSProps & {
  [k: string]: CSSProps[keyof CSSProps];
};

const cssProps: CSSProps = {fontWeight:500};
const props: Props = cssProps; // undesired $ExpectError

seems fine even with module augmentation in mind. Especially considering

- const props: Props = cssProps;
+ const props: Props = { ...cssProps };

is apparently safe for TS.

Is there a fix in place for this or is this ruled out as a design choice? @SeaRyanC

There are instances where we want to infer interfaces to more generic types. The vast majority of libraries out there prefer to use interface than types and in an instance where we’d like to infer them to more generic types, we can’t make the conversion.

A lot of these implementations are classes implementing interfaces so I’m not sure exactly how to get the best of both worlds, that is: using interfaces for classes to implement while still using utilities that accept a more generic type-structure (where it’s compatible) of an interface.

@OliverJAsh @JemarJones you can do something like this:

declare const requireObjectStrings: <T extends {[K in keyof T]: string}>(object: T) => void;

interface MyInterface {
  foo: string;
}

declare const myInterface: MyInterface;
requireObjectStrings(myInterface);

Of course, this workaround is annoying since you can’t create a concrete type for it.

Maybe something like this will help you:

declare const myInterface: MyInterface;
const myInterface2: Pick<MyInterface, keyof MyInterface> = myInterface;

requireObjectStrings(myInterface2)

Update: there’s something wrong

I summed up a model to understand this situation:

I assume that each ts type can be described by two sets, called the accept set and the offer set, each element of both sets is a ts object literal. A extends B if the offer set of type A is a subset of the accept set of B.

To describe these two sets, I created a small grammar, which I demonstrate below with some small examples:

examples
  1. [] Represents a collection with no elements in it

  2. [{ }] Represents a collection with only one object: {}

  3. [{ x: 0 }] Represents a collection with only one object: { x: 0 }

  4. [{ x: string }] Represents a collection containing all objects that satisfy the condition: there is only one key x, value is a subclass of string

    Contains: { x: '' }, { x: undefined }

    Does not contain: { x: 0 }, { x: '', y: '' }

  5. [{ (string)*: 0 }] Represents a collection containing all objects that satisfy the condition: there are zero or more keys, but keys must be subclasses of string

    Contains: { x: 0 }, { x: 0, y: 0 }, { 0: 0 }({ '0': 0 }), { }

  6. [{ (string - 'x' | 'y')*: 0 }] Represents a collection containing all objects that satisfy the condition: key must be a subclass of string, not 'x' | 'y'

  7. [{ (keyof any - 'x')*: 0, x: 1 }] Represents a collection containing all objects that satisfy the condition: Must contain 'x': 1. Values for keys other than 'x' must be 0

type A {
    x: 0
}
// A <- [{ x: 0 }] // accept set
// A -> [{ (keyof any - 'a')*: unknown, a: 3 }] // offer set

I also summarized some common definitions:

common definitions
type O = object;
interface IE { }
// <-> [{ (keyof any)*: unknown }] 

type TN = { [K in never]: unknown }; // == Record<never, unknown>
type TE = { };
// <- [{ (keyof any)*: unknown }]
// -> [{ }]

interface II { [K: string]: 0 }
// <-> [{ (keyof any - string)*: unknown, (string)*: 0 }]

type TI = { [K: string]: 0 };
type TMS = { [K in string]: 0 }; // == Record<string, 0>
// <- [{ (keyof any - string)*: unknown, (string)*: 0 }]
// -> [{ (string)*: 0 }]

interface IX { x: 0 }
// <-> [{ (keyof any - 'x')*: unknown, x: 0 }]

type TX = { x: 0 };
type TMX = { [K in 'x']: 0 }; // == Record<'x', 0>
// <- [{ (keyof any - 'x')*: unknown, x: 0 }]
// -> [{ x: 0 }]

Now let’s go back to the topic. In the common definition above, the offer set [{ x: 0 }] of TX is a subset of the accept set of II, while IX is not. For example, the offer set of IX can contain { y: 123 }, which is not a subset of the accept set of II. Hence TX extends II, but IX extends II does not hold.

Note

  1. The type of an object literal is the type of the corresponding type alias

    // type of a is A
    type A { x: 0 };
    var a = { x: 0 } as const;
    
  2. The above contents are all summarized in my experiments, and there may be errors in them.

What about this utility type?

@younho9 Also for deep recursive objects:

type DeepIndexSignature<O extends object> = {
  [P in keyof O]: O[P] extends object ? DeepIndexSignature<O[P]> : O[P]
}

interface Lar {
  coo: string;
  doo: string;
}

interface IFooBarCar {
  foo: string;
  bar: number;
  car: {
    lars: Lar[];
  };
}

declare const foobar3: IFooBarCar
declare const foobar4: DeepIndexSignature<IFooBarCar>

type RecursiveObj = string | number | boolean | RecursiveObj[] | { [k: string]: RecursiveObj }

declare function fn2(obj: RecursiveObj): void

fn2(foobar3)
fn2(foobar4)

Playground link

I would rather request expected behaviour to be like for interfaces. Otherwise, types can break runtime (we will have different types in TS and runtime)

Playground link

Code:

type A = {str: string}
type B = A & {count: number}

const b: B = {str: 'str', count: 1}
const a: A = b
const c: Record<string, string> = a
const d: string = c.count

console.log(typeof d)
// "number"

Sorry, might be little bit off-top, but i didn’t get why this type inference works:

interface Foo { foo: string }

const x = { foo: 'stuff', bar: 1 }

// does not error out, but should ?
const foo: Foo = x

// errors out as expected
const foo2: Foo = { foo: 'stuff', bar: 1 }

I just found myself in a very similar situation to the one described by @mcpatten (had an interface wrapped in a Promise<> that I wanted to widen) where it’s not possible to use the spreading operator trick. For anybody that comes across this, the solution in this case is to use type instead of interface to define your types, as it’s already been mentioned earlier in this thread.

Rather than this:

interface MyInterface {
  foo: string;
}

Can you do this?

type MyInterface = {
  foo: string;
}

I’m not sure what part of your question is in the third party library, and what you control.

in case it helps anyone, I just found a workaround (essentially converting the interface to a type via Pick) for a slightly different situation where I hit the same underlying issue:

declare function foo<T extends Record<string, unknown>>(foo: T): void;

interface SomeInterface {
    bar: string;
}

const value: SomeInterface = { bar: 'baz' };

// does not work
foo(value);

// does work and is still typesafe, if a bit cheesy
foo<Pick<SomeInterface, keyof SomeInterface>>(value);

and depending on the use case, I think it can be applied to the original issue:

interface IndexType {
     [key: string]: string;
}

interface doesNotWork {
    hola: string;
}
type doWorks = { hola: string };

let y: IndexType;

const correctA = { hola: "hello" };
const correctB: doWorks = { hola: "hello" };
//error should be assignable to y
// const error: doesNotWork = { hola: "hello " }; // this is the original type which fails below
const error: Pick<doesNotWork, keyof doesNotWork> = { hola: "hello " }; // this will work below

y = correctA;
y = correctB;
y = error; // no longer an error
y = {... error}; // original workaround (notes from original workaround: not equivalent since the instance is not the same)

^ Like it or not, implicit index signatures are an intended feature of TypeScript.

@RyanCavanaugh is there any news on this issue? I would have thought it would have been resolved by now as many more complex things have been built and released already.

Yes I know, sorry I meant that it should be assignable to Partial<Record<string, number>>. The use cases that I want this for is when I want to accept any object with that shape, since I generally will be acting on it via Object.entries and what not.

```ts
interface MyInterface {
  a: number
  b: number
}

const doesNotWork: { [key: string]: number } = {} as MyInterface
// Index signature is missing in type 'MyInterface'.

const doesWork: { [key: string]: number } = {} as { a: number, b: number }
type MyInterface= {
 a: number
 b: number
}
const doesNotWork: { [key: string]: number } = {} as MyInterface