Why can't an interface be assigned to Record<string, unknown>?
Asked Answered
T

4

53

I just noticed that an interface cannot be assigned to Record<string, unknown> (playground link):

interface Foo {
    foo: number
}

const foo: Foo = { foo: 1 }
const bar: Record<string, unknown> = foo
//    |-> Error: Type 'Foo' is not assignable to type 'Record<string, unknown>'
//               Index signature is missing in type 'Foo'.(2322)

However the same is possible, when the type declaration for Foo is omitted (playground link):

const foo = { foo: 1 }
const bar: Record<string, unknown> = foo // no error here

Question: Why is there a difference between both examples? For me the reduced type of the variable foo is the same in both examples... Shouldn't the interface Foo be assignable to Record<string, unknown>?

In my understanding Record<string, unknown> is equivalent to object and thus any interface should be assignable to it. Also @typescript-eslint/ban-types suggests using Record<string, unknown> instead of object.

Remarks: The first example works when either object (playground link) or Record<string, any> (playground link) is used instead of Record<string, unknown>.

Transformation answered 19/1, 2021 at 20:48 Comment(0)
C
51

You've run across Index signature is missing in type (only on interfaces, not on type alias) #15300

The code will work when you change an interface to a type:

type Foo = {
    foo: number
}

const foo: Foo = { foo: 1 };
const bar: Record<string, unknown> = foo;
Circumsolar answered 19/1, 2021 at 21:10 Comment(1)
Wow, that is incredibly counterintuitive.Jive
M
6

EDIT: @Lesiak has the correct answer above. I'm leaving this solely for the link to the related answer.

Admittedly, I'm a bit out of my depth here, but I'm looking through this answer and I see:

[A] big part of TypeScript's safety comes from the fact that [...] it'll only let you treat an object as a dictionary if it knows it's explicitly intended as one.

Which is consistent with my testing. Modifying your interface to explicitly treat Foo.foo as an index does not produce the error. (playground link)

interface Foo {
    [foo: string]: number
}

const foo = { foo: 1 }
const bar: Record<string, unknown> = foo

This doesn't answer your question fully, as Record<string, any> works with your explicit interface, but maybe someone more knowledgeable can take it from here.

Manzanares answered 19/1, 2021 at 21:6 Comment(2)
Actually interface Foo { foo: number, [key: string]: unknown } should be the right solution playground link since it means: "an object with a property foo of type number and it can be used as a dictionary"Transformation
In the linked thread there is a question why Record<string, any> from just 5 days ago: github.com/microsoft/TypeScript/issues/… :-) I also wonder why this works...Transformation
P
2

Record<string, unknown> requires an index signature (cf. this comment on GitHub).

So this fails to compile:

interface X {
  a: boolean;
  b: number;
}

const x: X = { a: true, b: 1 };
const y: Record<string, unknown> = x;  // error

And this compiles successfully:

interface X {
  [key: string]: unknown;
  a: boolean;
  b: number;
}

const x: X = { a: true, b: 1 };
const y: Record<string, unknown> = x;  // okay

This also compiles successfully because an index signature is inferred for type (cf. this comment on GitHub):

type X {
  a: boolean;
  b: number;
}

const x: X = { a: true, b: 1 };
const y: Record<string, unknown> = x;  // okay

This also compiles successfully because Record<string, any> does not require an index signature (cf. this comment on GitHub):

interface X {
  a: boolean;
  b: number;
}

const x: X = { a: true, b: 1 };
const y: Record<string, any> = x;  // okay
Partible answered 14/4, 2024 at 18:1 Comment(0)
H
0

This cleaner, and you don't have to mess with your interface / type definitions.

interface Foo {
    foo: number
}

type Bar = Record<string, unknown>

const foo: Foo = { foo: 1 }
const bar: Bar = { ...foo }
Haile answered 10/6, 2024 at 23:57 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.