What is the best way to explain a function that alters an object directly through reference?

I have a function that changes an object's properties directly:

function addProperty(object, newValue) {
  object.bar = newValue;
}

Is there a way in TypeScript to indicate that the type of object is modified after calling the addProperty function? I am looking for something like this:

interface UpdatedProperty<T> {
  bar: T;
}

function addPropertyMutatesTo<Original extends object, New>(object: Original transforms into (Original & UpdatedProperty<New>), newValue: New) {
  object.bar = newValue;
}

const dataObject = { foo: 'bar' };
dataObject.bar; // TS: error
addProperty(dataObject, 123);
dataObject.bar; // TS: ok

Answer №1

The type system in TypeScript is not primarily designed to mutate the types of variables, but it does utilize control flow analysis to narrow variable types within certain code blocks. However, until recently, there was no straightforward way to trigger such narrowing through functions like addProp().

With the introduction of assertion functions in TypeScript 3.7, developers now have a tool to create functions that guard against invalid states without returning a value. These assertion functions are relatively new and come with some limitations, as discussed in various GitHub issues.

One approach to achieve type narrowing with functions like addProp() is to define an assertion function where the input type T1 gets asserted to the narrower type T1 & WithProp<T2>. In this setup, if the input does not match the expected type, the function simply modifies the object to match that type by adding the necessary property.

function addProp<T1 extends object, T2>(
    o: T1,
    value: T2
): asserts o is T1 & WithProp<T2> {
    (o as T1 & WithProp<T2>).foo = value;
}

This allows for the desired behavior when accessing properties:

let obj = { bar: 'baz' };
obj.foo; // error! Property 'foo' does not exist on type {bar: string}
addProp(obj, 123);
obj.foo; // okay

It's important to note that this type narrowing occurs due to control flow analysis, which resets upon reassignments. This means that directly reassigning values might result in unexpected errors related to type mismatches.

Overall, while this method represents a step towards incorporating mutations in the type system, there are still limitations to consider. Good luck experimenting with these concepts!

Link to code example

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

Retrieve the property values of `T` using a string key through the `in

Having trouble accessing a property in an object using a string index, where the interface is defined with in keyof. Consider the following code snippet: interface IFilm { name: string; author: string; } type IExtra<T extends {}> = { [i ...

Unable to access pathways from a separate source

In my app.component.ts file, I have two router outlets defined, one with the name 'popup': @Component({ selector: 'app-main', template: `<router-outlet></router-outlet> <router-outlet name="popup" ...

Using TypeScript along with the "this" parameter

Hi there, I'm encountering an issue with the code snippet below. It keeps throwing an error message that says "Property 'weatherData' does not exist on type 'XMLHttpRequest'." The purpose of this code is to display weather informat ...

Issue deploying serverless: Module '/Users/.../--max-old-space-size=2048' not found

Everything is running smoothly on my serverless project locally when I use sls offline start. However, when I attempt to deploy it through the command line with serverless deploy, I encounter the following error stack. internal/modules/cjs/loader.js:1030 ...

Even as I create fresh references, my JavaScript array object continues to overwrite previous objects

Coming from a background in c# and c++, I am facing some confusion with a particular situation. Within the scope of my function, I am creating a new object before pushing it into an 'array'. Strangely, when I create the new object, it appears to ...

What is the process for generating an array of objects using JavaScript?

I am struggling to create an array of objects using JavaScript and facing errors with new lines added where I need to split the messages and collect row numbers. The row numbers should be comma-separated if it is a repetitive error message. I found a solu ...

The JSON.stringify method in TypeScript/JavaScript does not align with the json-String parameter or request body in a Java controller

Currently, I am utilizing jdk 1.8 and have a rest endpoint in my Java controller: @PostMapping("/filters") public ResponseEntity<StatsDTO> listWithFilter( @RequestBody(required = false) String filter ) { try { ............... } } A test sn ...

Is it necessary for me to set up @types/node? It appears that VSCode comes with it pre-installed

Many individuals have been adding @types/node to their development dependencies. Yet, if you were to open a blank folder in VSCode and create an empty JavaScript file, then input: const fs = require('fs'); // <= hover it and the type display ...

Unable to locate the name 'Cheerio' in the @types/enzyme/index.d.t file

When I try to run my Node application, I encounter the following error: C:/Me/MyApp/node_modules/@types/enzyme/index.d.ts (351,15): Cannot find name 'Cheerio'. I found a suggestion in a forum that recommends using cheerio instead of Cheerio. H ...

Can you explain the variances between the two Pick<T,K> util type implementations?

Here is a link I am exploring: https://github.com/type-challenges/type-challenges/blob/master/questions/4-easy-pick/README.md I am struggling to grasp the distinction between these two code snippets: type MyPick<T, K> = T extends {} ? K extends keyo ...

Get ready for 10 AM with the RxJS timer function

I am trying to figure out how to schedule a method in my code using rxjs/timer. Specifically, I want the method to run at precisely 10 AM after an initial delay of 1 minute. However, my current implementation is running every 2 minutes after a 1-minute d ...

The array within the JSON object holds vital information [Typescript]

I have some data stored in an Excel file that I want to import into my database. The first step was exporting the file as a CSV and then parsing it into a JSON object. fname,lname,phone Terry,Doe,[123456789] Jane,Doe,[123456788, 123456787] Upon convertin ...

Adjust the selected value in real-time using TypeScript

Hey there, I've got a piece of code that needs some tweaking: <div> <div *ngIf="!showInfo"> <div> <br> <table style="border: 0px; display: table; margin-right: auto; margin-left: auto; width: 155%;"& ...

A guide on organizing similar elements within an array using Angular

Could you assist me in grouping duplicate elements into separate arrays of objects? For example: array = [{key: 1}, {key: 5}, {key: 1}, {key: 3}, {key: 5}, {key: 1}, {key: 3}, {key: 2}, {key: 1}, {key: 4}]; Expected output: newArrayObj = {[{key: 1}, {key ...

Gaining entry to specialized assistance through an occasion

Having trouble accessing a custom service from a custom event. Every time the event is fired, the service reference turns out to be null. @Component({ selector: "my-component", template: mySource, legacy: { transclude: true } }) export class myComponent { ...

Tips for saving a variable in Angular that is being received through a subscription as JSON:

Below is an example of the json I have: [{"id":"1","date":"2020-02-21","status":"present","studentid":"1"},{"id":"2","date":"2020-02-24","status":"present","studentid":"1"}] I am struggling to store the date in a variable using Angular when it is being s ...

One approach to enhance a function in Typescript involves encapsulating it within another function, while preserving

What I Desire? I aim to create a function called wrap() that will have the following functionality: const func = (x: string) => 'some string'; interface CustomObject { id: number; title: string; } const wrapped = wrap<CustomObject> ...

What is the best way to see if a variable is present in TypeScript?

I am facing an issue with my code that involves a looping mechanism. Specifically, I need to initialize a variable called 'one' within the loop. In order to achieve this, I first check if the variable exists and only then proceed to initialize it ...

Vuejs fails to properly transmit data

When I change the image in an image field, the new image data appears correctly before sending it to the back-end. However, after sending the data, the values are empty! Code Commented save_changes() { /* eslint-disable */ if (!this.validateForm) ...

TypeScript functions with Generic Constraints return specific values rather than just types

function createGenericCoordinates<Type extends number | string>( x: Type, y: Type ) { return { x, y }; } const genericCoordinates = createGenericCoordinates(1, 2); // Error (TS2322) // Type 3 is not assignable to type 1 | 2 genericCoordinates ...