6

Here's an example of my issue (demo in ts playground)

type Tkey = 'foo' | 'bar' | 'defaultKey'

const typedObject: Record<Tkey, Boolean> = {
  'foo': true,
  'bar': false,
  'defaultKey': true
}

// type does not persist string narrowing
const entries = Object.entries(typedObject) // [string, Boolean][]

So key in [key, values] is of type string and not of type Tkey.

demo of resulting type

As a workaround you can manually coerce after the fact, but it'd be nice to define the type during the operation.

const typedEntries = entries as [Tkey, Boolean][]
blackgreen
  • 34,072
  • 23
  • 111
  • 129
Norfeldt
  • 8,272
  • 23
  • 96
  • 152
  • 3
    The reason behind this is an object could potentially have more properties at runtime compared to compile time. See this [comment](https://github.com/microsoft/TypeScript/pull/12253#issuecomment-263132208) on [TypeScript PR#12253](https://github.com/microsoft/TypeScript/pull/12253) for more information. In fact, the behaviour you want was previously merged in [PR#12207](https://github.com/microsoft/TypeScript/pull/12207) but later removed in [PR#12547](https://github.com/microsoft/TypeScript/pull/12547). – Wing Aug 11 '21 at 14:00
  • Also see this [question](https://stackoverflow.com/questions/55012174/why-doesnt-object-keys-return-a-keyof-type-in-typescript) for even more context. – Wing Aug 11 '21 at 14:06

2 Answers2

2

You can extend the default typescript lib by adding your own object definition that takes in two generic type params.

Add the following a globals.d.ts file at your project root:

interface ObjectConstructor {
    entries<TKey extends PropertyKey, TVal>(o: Record<TKey,TVal>): [TKey, TVal][];
    keys<TKey extends PropertyKey>(obj: Record<TKey,any>): TKey[];
}

And then use it like this:

const entries = Object.entries<ObjKeys, Boolean>(myObj) // [ObjKeys, Boolean][]
const keys = Object.keys<ObjKeys>(myObj) // ObjKeys[]

Demo in TS Playground

KyleMit
  • 30,350
  • 66
  • 462
  • 664
1

TypeScript's native definition files contain this for Object.entries:

entries<T>(o: { [s: string]: T } | ArrayLike<T>): [string, T][];

entries(o: {}): [string, any][];

Your only chance to get the behavior you want is to override this definition with something like this:

interface YourOwnObject {
  entries<T>(o: { [K in keyof T]: T[K] }): [keyof T, T[keyof T]][];
}

(<YourOwnObject>Object).entries(typedObject).forEach(([key, values]) => {
  // ...
})
Guerric P
  • 30,447
  • 6
  • 48
  • 86
  • Is there a way to do this globally? Can you imagine why this is not written into the native defintion?? – Norfeldt Aug 11 '21 at 13:51
  • I've edited with one possible way to do it. Unfortunately you can't modify the existing `ObjectInterface` and I have no idea why this is not written into the native definition files. – Guerric P Aug 11 '21 at 13:53
  • Do you have a link to native typescript source code? – Norfeldt Aug 11 '21 at 13:54
  • Yes: https://github.com/microsoft/TypeScript/blob/main/lib/lib.es2017.object.d.ts – Guerric P Aug 11 '21 at 13:57
  • You don't even need to imagine why this is not written in the native definition. There are written records as to why this isn't written into the native definition See my [comment](https://stackoverflow.com/questions/68742597/typescript-forgets-the-types-when-using-object-entriestypedobject-foreach#comment121489165_68742597) on the original post. – Wing Aug 11 '21 at 14:08
  • @Wing thanks a lot for all the links - there is a lot of history to this. Boy, I would wish that I could lot into having it typed – Norfeldt Aug 11 '21 at 17:12
  • isn't there a way to "cast"/state the types within the callback parameters? – Norfeldt Aug 12 '21 at 07:06
  • Do you know if Is there some sort of library that contains these type "fixes" for object? – Norfeldt Aug 14 '21 at 06:10
  • No I don't know anything like that, sorry – Guerric P Aug 15 '21 at 13:32