Home > Software engineering >  How to fix type narrowing that doesn't work on nested object?
How to fix type narrowing that doesn't work on nested object?

Time:01-16

In the code snippets below, the second last line in getChildEmail show no error here as child.email is no longer string|undefined but string only because of narrowing. However, type error is shown in last line of getChildEmail. This is unexpected as child's type should now satisfy the type of getEmail's argument.

type Parent = {
  name: string;
  email?: string;
  child: {
    name: string;
    email?: string;
  };
};

function getEmail({ name, email }: { name: string; email: string }) {
  return email;
}

function getChildEmail({ name, email, child }: Parent) {
  child.email.toLocaleLowerCase(); // error as expected, child.email is possibly undefined
  child.email && child.email.toLowerCase(); // working as expected, child.email is a string because of narrowing
  child.email && getEmail(child); // unexpected error as narrowing should make child satisfies the arguments of getEmail function
}

Why is it the case? What's the best practice to remove the last line type error?

You can also view it in the following typescript playground

CodePudding user response:

One option is to cast the type of child to the correct type with a type assertion

child.email && getEmail(child as Required<Parent['child']>);

Looks a bit cleaner if we define a Child type too

type Child = {
    name: string;
    email?: string;
}
type Parent = {
  name: string;
  email?: string;
  child: Child;
};

function getEmail({ name, email }: { name: string; email: string }) {
  return email;
}

function getChildEmail({ name, email, child }: Parent) {
  child.email?.toLocaleLowerCase();
  child.email && child.email.toLowerCase();
  child.email && getEmail(child as Required<Child>);
}

Playground

CodePudding user response:

One way to solve this is by adding a type guard (more about type guards, see here). Try something like this:

function hasEmail(person: { name: string; email?: string }): person is {name: string; email: string} {
  return person.email !== undefined;
}

And then check it in this way:

hasEmail(child) && getEmail(child);

See the entire fixed code here.

CodePudding user response:

type Parent = {
  name: string;
  email?: string;
  child: {
    name: string;
    email?: string;
  };
};

function getEmail({ name, email }: any ) {
  return email;
}

function getChildEmail({ name, email, child }: Parent) {
  child?.email?.toLocaleLowerCase();
  child.email && child.email.toLowerCase();
  child.email && getEmail(child);
}

check your return type in getEmail() function. I think, it causes an error. As I understood, you only want to return email property, not child object

  • Related