Restrict the object field type to an array by analyzing control-flow for accessing elements within brackets

Enhancements in type narrowing using control-flow analysis for bracket element access have been introduced in TypeScript 4.7.

One thing I'd like to do is verify if the accessed field is an array or not. Currently, the type guard seems to be ineffective:

Error: Type 'never[]' is not compatible with type 'string'.(2322)

https://i.stack.imgur.com/vZxID.png

Despite utilizing isArray, TypeScript still considers the accessed field as potentially a string type instead of an array.

What could be missing here?


Below is a detailed example:

Let's say we define a type called Post, where certain fields are arrays:

type Post = {
  id: string;
  title: string;
  chapters: PostChapter[];
};

Now, suppose we have a list of strings (keys of a Post) that we want to use to update the value in a Post object dynamically:

const fieldNamesToReplace: (keyof Post)[] = ["chapters"];

When attempting to access these keys using brackets, TypeScript fails to recognize them as arrays, even after validation through Array.isArray.

By the way, one workaround that functions is creating a new object and replacing the field, avoiding reliance on control-analysis for bracketed access.


Here is a link to the playground along with the complete example:

Playground link

type PostChapter = {
  id: string;
  chapterTitle: string;
};

type Post = {
  id: string;
  title: string;
  chapters: PostChapter[];
};

const fieldNamesToReplace: (keyof Post)[] = ["chapters"];

const posts: Post[] = [
  {
    id: "1",
    title: "abc",
    chapters: [{ id: "1.1", chapterTitle: "def" }],
  },
];

const postsTransformed = posts.map((post) => {
  let postNew = { ...post };

  // works, because we don't rely on the type-narrowing for setting the value
  fieldNamesToReplace.forEach((fieldName) => {
    if (Array.isArray(postNew[fieldName])) {
      postNew = { ...postNew, [fieldName]: [] };
    }
  });

  // doesn't work
  fieldNamesToReplace.forEach((fieldName) => {
    if (Array.isArray(postNew[fieldName])) {
      postNew[fieldName] = [];
      // Error: Type 'never[]' is not assignable to type 'string'.(2322)

      const placeholder = postNew[fieldName];
      //      ^?
    }
  });

  return postNew;
});


Answer №1

The enhancements focus on string literals and symbols. This signifies that it will not function when the key is a variable, which seems to be causing your issue.

To address this issue, you can utilize a type predicate, but keep in mind that you won't be able to assign values to that variable afterward.

Check out this playground to see a demonstration of this concept.

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

The argument type 'MouseEvent<HTMLButtonElement, MouseEvent>' cannot be assigned to the parameter type 'HTMLElementEvent<HTMLButton>'

Here is the code snippet that I am currently working on and encountering an error in the console: type HTMLElementEvent<T extends HTMLElement> = Event & { target: T; } toggleHandler = (e: HTMLElementEvent<HTMLButtonElement>) => ...

Vue Deep Watcher fails to activate when the data is altered

While the countdown timer is functioning properly, it seems that the deep watcher is not working as expected. Despite attempting to log the new value of seconds in the console, it does not display anything even though the countdown timer continues to funct ...

Should the PHP interface be exported to a Typescript interface, or should it be vice versa?

As I delve into Typescript, I find myself coding backend in PHP for my current contract. In recent projects, I have created Typescript interfaces for the AJAX responses generated by my backend code. This ensures clarity for the frontend developer, whether ...

Incorporate keyboard input functionality into an object wrapper

Adding typing to a class that encapsulates objects and arrays has been a bit tricky. Typing was easily implemented for objects, but ran into issues with arrays. interface IObject1 { value1: string, } interface IObject2 { myObject: IObject1, ...

What is the best way to capture the inputs' values and store them accurately in my object within localStorage?

Is there a more efficient way to get all the input values ​​and place them in the appropriate location in my object (localStorage) without having to individually retrieve them as shown in the code below? Below is the function I currently use to update ...

Dynamically setting properties in a Vue component using Angular

After browsing through this interesting discussion, I decided to create a web component: <my-vue-web-comp [userId]="1"></my-vue-web-comp> Initially, everything was working smoothly in Angular when I assigned a static property. Howeve ...

The chart JS label callback requires a specified type declaration

Currently, I am in the process of updating an old Angular platform to a newer version. One requirement is that everything must have its type declaration. I encountered a problem with the label callback showing this error: The error message reads: "Type &a ...

Transforming XML into Json using HTML string information in angular 8

I am currently facing a challenge with converting an XML document to JSON. The issue arises when some of the string fields within the XML contain HTML tags. Here is how the original XML looks: <title> <html> <p>test</p> ...

What is the best way to import a TypeScript file in index.js?

I've recently developed an application using the react-express-starter template. I have a "server" directory where the backend is created by nodejs, containing an index.js file: const express = require('express'); const app = express(); c ...

Error detected in Vuetify project's tsconfig.json file - The configuration file does not contain any input entries

Having an issue with the tsconfig.json file in my Vuetify project. The first { is underlined in red and when hovered over, it displays the error message: No inputs were found in config file Sharing the content of the file below. tsconfig.json { // expe ...

Creating a custom definition file for TypeScript

Currently, I am facing an issue with a third-party library that provides global functions similar to jQuery ($ .functionName()), but unfortunately there is no definition file available. Due to this, my attempt to write my own file has been unsuccessful as ...

Typescript Error:TS2345: The argument '{ theme: string; jsonFile: string; output: string; }; }' is not compatible with the parameter type 'Options'

Encountering an error mentioned in the title while using the code snippet below: import * as fs from 'fs' import { mkdirp } from 'mkdirp' import * as report from 'cucumber-html-reporter' const Cucumber = require('cucumber ...

How should one approach working with libraries that do not have type definitions in TypeScript?

My current situation involves working with libraries that are untyped and resulting in warnings. I am curious about the best approach to address this issue - should I adjust configurations, use tslint ignore on a line-by-line basis, or possibly create du ...

The getMonthlyBalances function is providing inaccurate results when calculating stock balances

One of my functions is called getMonthlyBalances, which takes in two arrays - stocks and trades. It calculates the monthly balance of a user's stock holdings based on their trade history. The stocks array includes objects with stock ID, prices, and da ...

Ways to modify the datepicker format in Angular Material

I am currently facing an issue with the date format generated by the angular material datepicker...Wed Nov 21 2018 00:00:00 GMT+0530 (India Standard Time) My requirement is to receive the date in either (YYYY-MM-DD) or (YYYY-MM-DDTHH:mm) format. Here is ...

Typescript - Certain implementations may eliminate the optionality of properties

After exploring multiple versions of the Omit implementation, including the one displayed by Intellisense when hovering over Omit, I'm struggling to grasp why certain implementations are homomorphic while others are not. Through my investigation, I&a ...

Tips on sorting a FileList object selected by a directory picker in JavaScript/TypeScript

I need to filter or eliminate certain files from a FileList object that I obtained from a directory chooser. <input type="file" accept="image/*" webkitdirectory directory multiple> Within my .ts file: public fileChangeListener($event: any) { let ...

Dynamically divide canvas screens based on fabricjs dropdown selection

I am attempting to implement split screens in fabric js, such as 1, 2, 4, 8, and 16. The screen should split based on the selection from the dropdown menu. Check out my current code where I have successfully uploaded images. If I click on the images, th ...

The value is currently unset in the TypeScript language

The variable `this.engenes_comparte` is showing up as undefined inside the subscribe function, but it works fine outside of it. baja(){ this._restService.getEngines(this._globalService.currentFisherMan.nid).subscribe((data : any[]) => { le ...

Encountering a type-safety problem while attempting to add data to a table with Drizzle

My database schema is structured like so: export const Organization = pgTable( "Organization", { id: text("id").primaryKey().notNull(), name: text("name").notNull(), createdAt: timestamp("c ...