Associate the keys of a map interface with an array containing key-value pairs

Consider a scenario where we have an interface

For example:

interface Person {
    name: string;
    age: number ;
}

We aim to develop a generic function that can take the following parameters


const res = Result.combineValues<Person>(
    { age: 18 },
    { name: 'John Doe' }
);

At this stage, our current implementation looks like this


class Result<T> {

    readonly value: T;

    private constructor(value: T) {
        this.value = value;
    }

    public static combineValues<T>(...results: { [key in keyof T]?: Result<T[key]> | T[key] }[]): Result<T> {
        let value: T;
        // ... compute value
        return new Result<T>(value);
    }
}


The issue we are facing is that the function allows undefined values


const res = Result.combineValues<Person>(
    { age: undefined }, // This should throw a compile error as age must be a number or Result<number>
    { name: 'John Doe' }
);

Moreover, it does not ensure that all properties are defined


const res = Result.combineValues<Person>(
    { age: 18 } 
    // A compile error should be raised here because `{name: 'Some Name'}` is missing from the argument list
); 

Answer №1

Typescript lacks a built-in feature to break down union or intersection types into their individual components, or to specify that a set of unknown types should collectively form a known type.

Due to this limitation, attempting to make a function generic based on the Result type and imposing conditions on the variadic argument tuple will not yield the desired outcome.

Instead, we can make the function generic on the tuple of types it receives, infer the return type based on the intersection of those types, and enforce compile-time constraints by assigning the result to a variable of type Result<Person>.

In order to handle instances of the Result class, we introduce a type to replace all members of a type that are Result<T> with just T.

type ReplaceResultsIn<Obj> = {
  [Key in keyof Obj]: Obj[Key] extends Result<infer T> ? T : Obj[Key]
}
type Combine<T extends unknown[]> = T extends [infer First, ...infer Rest] ? (ReplaceResultsIn<First>) & Combine<Rest> : unknown

We then define the combineValues function to return

Combine< the argument types >
:

class Result<T> {
  readonly value: T;

  public constructor(value: T) {
    this.value = value;
  }
  public static combineValues<T extends object[]>(...args: T): Result<Combine<T>> {
        let value!: Combine<T>;
        // ... calculate value
        return new Result<Combine<T>>(value);
    }
}

Clients using this class can now operate with type safety:

interface Person {
  name: string;
  age: number;
};

// valid assignments
const person: Result<Person> = Result.combineValues({age: new Result(21)}, {name: ""});
const person2: Result<Person> = Result.combineValues({age: 21, name: ""});
const person3: Result<Person> = Result.combineValues({age: 21}, {name: ""});
const person4: Result<Person> = Result.combineValues({name: new Result("")}, {age: new Result(21)});

// invalid assignments
const person5: Result<Person> = Result.combineValues({});
const person6: Result<Person> = Result.combineValues({age: 21});
const person7: Result<Person> = Result.combineValues({name: 21}, {age: 21});
const thing = Result.combineValues("");
const thing2 = Result.combineValues(null);

Explore further on Playground

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What is the best way to transfer information from a service to JSON format?

Currently, I am developing an Angular 6 application that involves creating a dynamic form using data obtained from a JSON file. JSON Data: jsonData: any = [ { "elementType": "textbox", "class": "col-12 col-md-4 col-sm-12", "key": ...

The custom component is not updating the NgIf directive in HTML even though it receives a boolean variable

I am struggling with a custom component that includes an *ngIf in its view to handle a boolean variable, but for some reason the *ngIf directive is not working. Here is the code snippet: Component @Input('title') titleText; @Input('backButt ...

CDK Error: Unable to locate MethodResponse in AWS API Gateway configuration

I'm facing an issue in vscode while trying to access the MethodResponse interface from apigateway. Unfortunately, I'm getting an error message: The type 'typeof import(".../node_modules/aws-cdk-lib/aws-apigateway/index")' d ...

What is the best way to reference class variables and methods within a callback function in Typescript?

While working on my Angular project with the Highcharts API, I encountered a situation where I needed to pass a state code to a class level method after drilling down to a specific map location. Below is the snippet of my current code: ngOnInit() { this. ...

Creating a default option in a Select tag with React when iterating over elements using the map method

After learning that each element in the dropdown must be given by the Option tag when using Select, I created an array of values for the dropdown: a = ['hai','hello','what'] To optimize my code, I wrote it in the following ...

Having trouble with generic typescript props in React? It seems like typescript generic props are not functioning as expected. Let's explore

In my React project, I am facing a challenge. I have a functional component where I pass down a single prop: <TableComponent tableStateProp={tableState} /> The `tableState` is a state hook declared in the parent component like this: const [tableSt ...

When I apply flex to the display of .ant-steps-item-icon, the connection between the steps vanishes

I recently utilized ANTD and React to develop a customized component step style, but ran into an issue. Here is the CSS code snippet I used: /* step connector */ .ant-steps-item-title:after { border: solid rgba(65, 64, 66, 0.1) !important; } /* step * ...

Only the final defined document is instantiated by the Swagger-ui-express module

Currently, I am working on a Typescripted nodejs server and facing an issue with defining different swagger paths for separated controllers. The problem is that the swagger-ui-express module only displays the last defined document in the specific route. I ...

Issue with data not refreshing when using router push in NextJS version 13

I have implemented a delete user button on my user page (route: /users/[username]) which triggers the delete user API's route. After making this call, I use router.push('/users') to navigate back to the users list page. However, I notice tha ...

Exploring the world of interfaces in nested mapping functions

Currently, I'm faced with the challenge of mapping an array inside another map with an array. These are the specific interfaces that I am working with: interface Props { columns: Array<{field: string, headerName: string, width: number}>; row ...

"Launching" conduit for Observable

Is there a way to attach two pipes to an HttpClient request in order to execute functions at the beginning and end of the request? I have discovered the "finalize" operator for executing a function when the request is finished, but I am looking for an equi ...

Access the RxJS subscription data once and save it for later reuse

Currently, I am retrieving plans from a service using RxJS: public class PlansListComponent implements OnInit { private plans$: Subject<PlanDTO> = new BehaviorSubject([]); ngOnInit():void { this.serverService .list() .subscribe( ...

Reacting to the dynamic removal of spaces

Is there a way to dynamically remove the space between removed chips and older chips in Material-UI when deleting one of them? <div className="row contactsContainer"> {contacts.map((contac ...

What is the process for calling a recursive constructor in TypeScript?

I am working on a class constructor overload where I need to recursively invoke the constructor based on the provided arguments. class Matrix { /** * Construct a new Matrix using the given entries. * @param arr the matrix entries */ ...

Issue: Unable to import certain modules when using the Typescript starter in ScreepsTroubleshooting: encountering

Having trouble with modules in the standard typescript starter when transferring to screeps. One issue is with the following code: import * as faker from 'faker'; export function creepNamer() { let randomName = faker.name.findName(); return ...

In TypeScript, the nested object structure ensures that each property is unique and does not repeat within

Here is the current state of my interface. I am wondering if there is a way to streamline it to avoid repeating properties in both parts. export interface Navigation { name: string; roles: Array<number>; sublinks: NavigationItem[]; } ...

Jest: Test fails due to import statement in node_module dependency

Short Version I'm experiencing a crash in my Jest test due to a SyntaxError related to an import statement outside a module. The issue arises from a node_module that uses the import statement. How can I resolve this error? Situation Overview In deve ...

Displaying nested objects within an object using React

Behold this interesting item: const [object, setObject] = useState ({ item1: "Greetings, World!", item2: "Salutations!", }); I aim to retrieve all the children from it. I have a snippet of code here, but for some reason, i ...

Discovering the ReturnType in Typescript when applied to functions within functions

Exploring the use of ReturnType to create a type based on return types of object's functions. Take a look at this example object: const sampleObject = { firstFunction: (): number => 1, secondFunction: (): string => 'a', }; The e ...

The attribute 'size' is not recognized within the data type 'string[]' (error code ts2339)

When using my Windows machine with VSCode, React/NextJS, and Typescript, a cat unexpectedly hopped onto my laptop. Once the cat left, I encountered a strange issue with my Typescript code which was throwing errors related to array methods. Below is the co ...