javascript - Removing undefined from ES6 mapping without Typescript errors - Stack Overflow

When an object has an id that could be undefined and you want to select all the existing ids from an ar

When an object has an id that could be undefined and you want to select all the existing ids from an array of those objects, why does the following produce a Typescript error saying y.id may be undefined?

.filter(x => x.id !== undefined).map(y => y.id)

When an object has an id that could be undefined and you want to select all the existing ids from an array of those objects, why does the following produce a Typescript error saying y.id may be undefined?

.filter(x => x.id !== undefined).map(y => y.id)
Share Improve this question asked Oct 19, 2020 at 9:57 user3775501user3775501 2081 gold badge4 silver badges16 bronze badges 11
  • 2 Because .filter doesn't change the type of your array. If the type is Array<{id?: number}> then filtering also produces an array of type Array<{id?: number}>. After all, TS can't properly analyse the logic of your filtering function - you can also do .filter((x, index) => index % 2 === 1) and you might still have missing IDs in your dataset. – VLAZ Commented Oct 19, 2020 at 10:08
  • 1 What is the type of the object? If it is an union type, you may try using type predicate – sup39 Commented Oct 19, 2020 at 10:22
  • 1 You can probably just do map(y => y.id!) – Evk Commented Oct 19, 2020 at 10:29
  • 2 @user3775501 I mean change your existing code in this way, not just do map. So, .filter(x => x.id !== undefined).map(y => y.id!). First you filter out undefined values, then while mapping you assert to the piler that you are sure y.id is defined, via ! operator. – Evk Commented Oct 19, 2020 at 10:37
  • 2 Well other languages with type checking are likely to be the same. For example, in C# if you have array of nullable int (int?), and do the same as you do in typescript (.Where(x => x != null).Select(y => y) then result will also be array of nullable ints. Compiler just cannot reliably check what you are doing inside filter expression, so you have to help him (in case of C# by doing Select(y => y.Value), which is quite close to typescript way of ensuring piler it cannot be null. YOU know all values are defined, but piler does not. – Evk Commented Oct 19, 2020 at 10:45
 |  Show 6 more ments

1 Answer 1

Reset to default 7

The problem is that .filter() doesn't automatically change the type of the array. If you have Array<T> then .filter() produces Array<T> again. After all, it's not easy to determine what are you filtering. Consider this:

const input = [
  { id: 1, name: "Alice" },
  { id: 2, name: "Bob" },
  { name: "Carol" },
  { name: "David" },
];

const result = input.filter(x => x.name.includes("o"));

console.log(result);

Would it be right to change the type from Array<{id?: number, name: string}> to Array<{id: number, name: string}>? No, it wouldn't. The filter clearly still produces items that might not have id property. If you don't know the data, then it's even harder to determine what the filtering does and what it's meant to do.

However, TypeScript does have a typing for

interface Array<T> {
    filter<U extends T>(pred: (a: T) => a is U): U[];
}

Or a .filter() method that transforms from Array<T> to Array<U>. It accepts a type guard which should tell it that the filtering produces a new type. So, what you can do is use a type guard that will convince the TypeScript piler that the following the operation, you have IDs. Note that type guards have to return a boolean:

interface MyType {
  id?: number;
  name: string;
}

input
  .filter((x): x is MyType & {id: number} => "id" in x)
  .map(y => y.id);

Playground Link

However, this will not catch the cases where the id property might be present but null or undefined for example:

interface MyType {
  id?: number | null | undefined;
  name: string;
}

So, you need to modify the logic to (x): x is MyType & {id: number} => "id" in x && typeof x.id === "number". This starts to get unwieldy and it's not easily reusable if you have a different type like

interface Foo {
  id?: number | null | undefined;
  bar: string
}

You can generalise the type guard using generics so it will check any type that might have an id:

type IdType<T extends {id?: any}> = T["id"]; //what is the type of the `id` property
type HasId<T> = T & {id: Exclude<IdType<T>, null | undefined>}; // T with a mandatory and non-null `id` property

function hasId<T extends { id?: any }>(item: T): item is HasId<T>{
  return "id" in item        // has `id`
    && item.id !== undefined // isn't `undefined`
    && item.id !== null;     // isn't `null`
}

Playground Link

This will allow you to filter arrays which contain types with id easily:

const result: number[] = input
  .filter(hasId)
  .map(y => y.id);

Playground Link

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1744735419a4590716.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信