38

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?

ktretyak
  • 27,251
  • 11
  • 40
  • 63

4 Answers4

22

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.

aacotroneo
  • 2,170
  • 16
  • 22
13

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)

elnygren
  • 5,000
  • 4
  • 20
  • 31
  • For me your RequiredKeys utility type didn't work as expected. This version did the trick: ```type RequiredKeys = Exclude & { [key in K]-?: Required }``` – Antoine Laffargue Dec 08 '20 at 12:25
4

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);
}
Nitzan Tomer
  • 155,636
  • 47
  • 315
  • 299
  • Thanks, I know about type guard, but its work for a bit different purpose, because I do not need check type of the argument. I have two compatible objects and I believe that exists way to clarify this fact for TypeScript. – ktretyak Jan 11 '17 at 18:22
  • But they are not compatible. `{ a: any }` is not the same type as `{ a?: any }`. You have no way of clarifying that for the compiler other than type assert or type guard. – Nitzan Tomer Jan 11 '17 at 18:37
2

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.

Kuba Jagoda
  • 4,908
  • 2
  • 19
  • 21
  • If you treat my problem seriously, then perhaps it will be easier to understand. For example, if I use an external library, I can not change definition for `interface Second` but its still compatible with `interface First`. – ktretyak Jan 11 '17 at 18:03
  • The point is that interface `Second` is not compatible with the interface `First` because of the optional property. Regardless of that, all the 3 solutions I presented work in this case. – Kuba Jagoda Jan 11 '17 at 18:26