ktretyak
ktretyak

Reputation: 31799

How to tell TypeScript that optional property in object exists and is set?

I have the following code:

interface First
{
  propertyA: string;
}

// Here propertyA is optional
// Imagine that this interface came from external library.
interface Second
{
  propertyA ?: string;
}

function fn(arg: First)
{
  // ...
}

// I know that this object can be declared as type of First,
// but I really need set this as type of Second interface
let myVar: Second = {propertyA: 'some string'};

// I really need in this way to make the call.
fn(myVar); // Error

if(myVar.hasOwnProperty('propertyA'))
{
  fn(myVar); // Still same error
}

if(myVar.propertyA)
{
  fn(myVar); // Still same error
}

But TypeScript throw error:

Argument of type 'Second' is not assignable to parameter of type 'First'. Property 'propertyA' is optional in type 'Second' but required in type 'First'.

So, how to tell TypeScript that optional property propertyA in myVar exists and is set?

Upvotes: 43

Views: 48148

Answers (4)

aacotroneo
aacotroneo

Reputation: 2220

old question, but there's a very clean solution in newer versions of typescript

fn(myVar!);

In Typescript, what is the ! (exclamation mark / bang) operator when dereferencing a member?

EDIT: This doesn't answer the original question in which the OP wanted to tell Typescript that propertyA is defined inside myVar. This solution was biased by the misleading title, and it only shows how to instruct Typescript that a given var (myVar!) or property (myVar.propertyA!) is defined.

It seems many people come here by looking at the title and find this answer helpful.

Upvotes: 24

elnygren
elnygren

Reputation: 5355

This problem is probably more generally about creating a type guard that tells the compiler your value is of a new type where said field is NOT optional but mandatory / required.

One way is to use the Required<T> type shipped with TypeScript that flips all fields to become required. However, a more realistic scenario is that perhaps not all, but only some fields are checked to exist.

Here's an example of a generic type and a typeguard for such a case:

    /** Interface with optional properties */
    interface IOptionalData {
      foo?: { bar?: string };
      other?: { bar?: string};
      always: { bar?: string };
    }

    /** Utility to make certain keys of a type required */
    type RequiredKeys<T, K extends keyof T> = Exclude<T, K> & Required<Pick<T, K>>

    /** Typeguard for property 'foo' in IOptionalData */
    const ensureFooProperty = (data: IOptionalData): data is RequiredKeys<IOptionalData, 'foo'> =>
      !!data.foo && typeof data.foo.bar === 'string'

    const accessData = (data: IOptionalData) => {
      if (ensureFooProperty(data)) {
        console.log(data.always.bar) // always is always defined
        console.log(data.other.bar)  // COMPILER ERROR: 'other' is possibly undefined
        return data.foo.bar          // accessing optional props is allowed due to ensureToFoo
      }
      console.log(data.foo.bar)      // COMPILER ERROR: 'foo' is possibly undefined
    }

https://gist.github.com/elnygren/ddd28c2f0d737d8a1130da783426fea7

Note: in my example you could always just inline the check into the if statement, however, this is not always the best course of action due to DRY (your type guard might be quite a bit more complex)

Upvotes: 14

Nitzan Tomer
Nitzan Tomer

Reputation: 164457

You can do this:

fn(myVar as First);

And use a type guard for the if:

function isFirst(obj: any): obj is First {
    return obj && obj.propertyA;
}

if(isFirst(myVar)) {
  fn(myVar);
}

Upvotes: 5

Kuba Jagoda
Kuba Jagoda

Reputation: 5557

I don't understand why you declare it as type Second since it has the property. However, you can do one of the following:

  • change the type in the declaration to First, i.e. let myVar: First = {propertyA: 'some string'};
  • drop the type declaration completely. Then it will receive an anonymous type { propertyA: string; } and will be assignable to First, i.e. let myVar = {propertyA: 'some string'};
  • use explicit type casting, i.e. fn(<First>myVar);

The error is caused because it's not safe to assume optional property will be there.

Upvotes: 2

Related Questions