Reputation: 31799
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
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
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
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
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:
First
, i.e. let myVar: First = {propertyA: 'some string'};
{ propertyA: string; }
and will be assignable to First
, i.e. let myVar = {propertyA: 'some string'};
fn(<First>myVar);
The error is caused because it's not safe to assume optional property will be there.
Upvotes: 2