Typescript Key-Value Relation Preserving Object. Entries Type

Typescript Key-Value Relation Preserving Object. Entries Type

The typing for Object.entries provided by typescript has the return type [string, T][] but I am searching for a generic type Entries<O> to represent the return value of this function that keeps the relationship between the keys and the values.

Eg. when having an object type like

type Obj = {
    a: number,
    b: string,
    c: number
}

I'm looking for a type Entries<O> that results in one of the types below (or something similar) when provided with Obj:

(["a", number] | ["b", string] | ["c", number])[]
[["a", number], ["b", string], ["c", number]]
(["a" | "c", number] | ["b", string])[]

That this isn't correct for all use cases of Object.entries (see here) is no problem for my specific case.


Tried and failed solution:

type Entries<O> = [keyof O, O[keyof O]][] doesn't work for this as it only preserves the possible keys and values but not the relationship between these as Entries<Obj> is ["a" | "b" | "c", number | string].

type Entry<O, K extends keyof O> = [K, O[K]]
type Entries<O> = Entry<O, keyof O>[]

Here the definition of Entry works as expected eg. Entry<Obj, "a"> is ["a", number] but the application of it in the second line with keyof O as the second type variable leads again to the same result as the first try.

2 Answers

Here's a solution, but beware when using this as a return type for Object.entries; it is not always safe to do that (see below).


When you want to pair each key with something dependent on that key's type, use a mapped type:

type Entries<T> = {
    [K in keyof T]: [K, T[K]];
}[keyof T][];

type Test = Entries<Obj>;
// (["a", number] | ["b", string] | ["c", number])[]

The second version, which has a tuple type containing the properties instead of a union, is much harder to construct; it is possible to convert a union to a tuple but you basically shouldn't do it.

The third version is manageable, but a bit more complicated than the first version: you need PickByValue from this answer.

type Entries3<T> = {
    [K in keyof T]: [keyof PickByValue<T, T[K]>, T[K]]
}[keyof T][];

type Test3 = Entries3<Obj>;
// (["a" | "c", number] | ["b", string])[]

Playground Link


I guess I should also explain why Typescript doesn't give a stronger type to Object.entries. When you have a type like type Obj = {a: number, b: string, c: number}, it's only guaranteed that a value has those properties; it is not guaranteed that the value does not also have other properties. For example, the value {a: 1, b: 'foo', c: 2, d: false} is assignable to the type Obj (excess property checking for object literals aside).

In this case Object.entries would return an array containing the element ['d', false]. The type Entries<Obj> says this cannot happen, but in fact it can happen; so Entries<T> is not a sound return type for Object.entries in general. You should only use the above solution with Object.entries when you yourself know that the values will have no excess properties; Typescript won't check this for you.

1

we can make dedicated function as below:

depictObjectKeyType<O>(o: O) {
    return Object.keys(o) as (keyof O)[];
}

depictEntriesKeyType<T>(obj: T): Entries<T> {
    return Object.entries(obj) as any;
}

and use as:

this.depictEntriesKeyType(data).forEach(....

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Robert Thorne
Author

Robert Thorne

Robert Thorne covers electric vehicle innovations, autonomous driving systems, global mobility trends, and automotive engineering developments.