TrevTheDev
TrevTheDev

Reputation: 2747

How to narrowing a single property on an object

How does one narrow a single property on an object?

type Foo = { 
  a: number | undefined
  b: string
}

let foo: Foo 
foo = { 
  a: 1,
  b: 'b'
}

// function accepts only narrowed version of Foo
const fn = (arg: Foo & { a: number })=>console.log(arg.a)

if(foo.a!==undefined){
  // compiler should know foo's type is now { a: number, b: string }
  fn(foo)
}

One solution would be to manual override the type as follows:

fn(foo as Foo & { a: number })

However is there a better way?

code

Upvotes: 1

Views: 52

Answers (2)

Gaurang Patel
Gaurang Patel

Reputation: 532

Typescript does not narrow object types based on conditional checks on object properties. You could achieve this by creating a user-defined type guard that checks on typeof Foo.a like following:

type Foo = { 
  a: number | undefined
  b: string
}
//custom type guard using type predicate
function fooHasA(obj: Foo): obj is Foo & {
    a: Exclude<Foo["a"], undefined>
} {
    return typeof obj.a !== 'undefined';
}

let foo: Foo 
foo = { 
  a: 1,
  b: 'b'
}

// function accepts only narrowed version of Foo
const fn = (arg: Foo & { a: number })=>console.log(arg.a)

if(fooHasA(foo)){
  // compiler knows now that foo's type is now { a: number, b: string }
  fn(foo)
}

TS playground

Also, you don't need to create new types just for the sake of narrowing an object.

Upvotes: 1

Schwern
Schwern

Reputation: 165586

Since you only need a the obvious thing to do is to redefine your function to take a number.

const fn = (arg: number)=>console.log(arg)

if(foo.a){
  fn(foo.a)
}

Another option is to use a type predicate.

// define a new type just to make things tidy
type FooWithA = Foo & { a:number }

function isFooWithA(obj: Foo): obj is FooWithA {
    return obj.a !== undefined;
}

const fn = (arg: FooWithA)=>console.log(arg.a)

if(isFooWithA(foo)) {
  fn(foo);
}

What might be best is to rethink your types to eliminate the optional parameter.

type A = {
    a: number
}

type B = {
    b: string
}

type AB = A & B

const fn = (arg: A)=>console.log(arg.a)

const foo: AB = {
    a: 1,
    b: "two"
};
fn(foo)

A more detailed example is necessary to know which you should use.

Upvotes: 1

Related Questions