Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I infer an object property's value in TypeScript when using mapped types?

I'm trying to create a singleton factoryLookup in typescript that enforces that I have a factory defined for every value in a union type (i.e. a mapped lookup type), however I also want to be able to infer the signature (could be a value or function of arbitrary arguments) for any value that is looked up. Here is a code sample:

// Using a simple object, I can create a map between a key and an arbitrary function or value
const factoryLookup = {
  foo: (message: string, count: number): string => message + count,
  bar: (): number => 1,
};

// TypeScript can infer function signature of the foo and bar values
factoryLookup.foo('hello world', 3); // foo's signature: (message: string, count: number) => string
factoryLookup.bar(); // bar's signature: () => number

type FactoryTypes = 'foo' | 'bar' | 'baz';

// With a mapped type, TypeScript can enforce I have *something* defined for every member I am mapping
const typedFactoryLookup: { [key in FactoryTypes]: any } = {
  foo: (message: string, count: number) => {},
  bar: () => {},
  baz: (obj: any) => {},
};

// However, I don't know how to infer the mapped values
typedFactoryLookup.foo; // any type
typedFactoryLookup.bar; // any type
typedFactoryLookup.baz; // any type

I want the type safety of ensuring I have a function defined for every value in my union/enum type, but also the type inference to know what the signature of that value is.

Any ideas on how to solve this, or alternative approaches that achieve the same goals would be appreciated.

like image 295
Craig Smitham Avatar asked Aug 08 '18 15:08

Craig Smitham


People also ask

How do you access the properties of objects in TypeScript?

To dynamically access an object's property: Use keyof typeof obj as the type of the dynamic key, e.g. type ObjectKey = keyof typeof obj; . Use bracket notation to access the object's property, e.g. obj[myVar] .

How do you type infer TypeScript?

Using infer in TypeScript It does that by first checking whether your type argument ( T ) is a function, and in the process of checking, the return type is made into a variable, infer R , and returned if the check succeeds: type ReturnType<T> = T extends (... args: any[]) => infer R ?

How do you assign a value to an object in TypeScript?

To use the Object. assign() method in TypeScript, pass a target object as the first parameter to the method and one or more source objects, e.g. const result = Object. assign({}, obj1, obj2) . The method will copy the properties from the source objects to the target object.

What is a mapped object type TypeScript?

A mapped type is a generic type which uses a union of PropertyKey s (frequently created via a keyof ) to iterate through keys to create a type: type OptionsFlags < Type > = { [ Property in keyof Type ]: boolean; };

What does it mean to map a type in typescript?

Here, we’ve mapped each number in the array to its string representation. So a mapped type in TypeScript means we’re taking one type and transforming it into another type by applying a transformation to each of its properties. TypeScript authors can access the type of a property by looking it up by name:

What are readonly properties in typescript and how to remove them?

It’s useful to signal intent during development time for TypeScript on how an object should be used. TypeScript doesn’t factor in whether properties on two types are readonly when checking whether those types are compatible, so readonly properties can also change via aliasing. Using mapping modifiers, you can remove readonly attributes.

What is the infer primitive in typescript?

Let’s now check out the infer primitive added on version 2.8. TypeScript relies heavily on type inference. There is a infer primitive which empowers your mapped types with explicit inference. You can use it to extract and a type inside a conditional type.

What does the infer keyword DO in typescript?

The infer keyword compliments conditional types and cannot be used outside an extends clause. Infer allows us to define a variable within our constraint to be referenced or returned. Take the built-in TypeScript ReturnType utility, for example. It takes a function type and gives you its return type:


1 Answers

If you explicitly type a variable, that will be its type no inference will occur. You can get the behavior you want, but you need to use the inference behavior of functions.

type FactoryTypes = 'foo' | 'bar' | 'baz';


function createTypedFactoryLookup<T extends { [key in FactoryTypes]: any }>(factory: T) {
    return factory;
}
const typedFactoryLookup = createTypedFactoryLookup({
    foo: (message: string, count: number) => {},
    bar: () => {},
    baz: (obj: any) => {},
})

typedFactoryLookup.foo; // (message: string, count: number) => void
typedFactoryLookup.bar; // () => void
typedFactoryLookup.baz; // (obj: any) => void

You can also create a version that takes in the type for the keys, but you will need to use a function that returns a function as typescript does not support specifying only some type parameters (it might be supported in 3.1)

function createTypedFactoryLookup<TKeys extends string>(){
    return function <T extends { [key in TKeys]: any }>(factory: T) {
        return factory;
    }
}
const typedFactoryLookup = createTypedFactoryLookup<FactoryTypes>()({
    foo: (message: string, count: number) => {},
    bar: () => {},
    baz: (obj: any) => {},
})
like image 89
Titian Cernicova-Dragomir Avatar answered Oct 12 '22 04:10

Titian Cernicova-Dragomir