artidataio
artidataio

Reputation: 361

How to fix type narrowing that doesn't work on nested object?

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

Upvotes: 1

Views: 273

Answers (3)

Shalom Peles
Shalom Peles

Reputation: 2632

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.

Upvotes: 1

Iain Shelvington
Iain Shelvington

Reputation: 32244

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

Upvotes: 1

yeskermesovv
yeskermesovv

Reputation: 1

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

Upvotes: 0

Related Questions