Narrow down type of object field to type array via control-flow analysis for bracketed element access

Viewed 24

Type narrowing with control-flow analysis for bracket element access has been improved with TypeScript 4.7.

What I additionally would like to do is check whether the accessed field is an array or not. Right now, I don't know why the type-guard does not work:

Error: Type 'never[]' is not assignable to type 'string'.(2322)

enter image description here

As you can see in the placeholder comment, TypeScript still thinks that the accessed field could be of type string, even though isArray should've made clear that the field is some kind of array.

What am I missing?


Here is a complete example:

Imagine we have a Post type, where some of the fields are arrays:

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

Now, I have list of strings (keys of a Post), which I want to use to overwrite the value in a Post object dynamically:

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

When using that keys with bracketed access, TypeScript doesn't know that it is an array, even when you check for it via Array.isArray.

Btw: What does work (as a workaround?) is just creating a new object and overwriting the field, as we don't rely on the control-analysis for bracketed access.


Here is a playground link and the full 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;
});


1 Answers

The improvements work on string literals and symbols. This means that it won't work when the key is a variable, hence your problem.

You can use a type predicate to fix this, but you won't be able to assign to that variable though.

Here's a playground that showcases this.

Related