624

When I make any property of an interface optional, and while assigning its member to some other variable like this:

interface Person {
  name?: string,
  age?: string,
  gender?: string,
  occupation?: string,
}

function getPerson() {
  let person = <Person>{name:"John"};
  return person;
}

let person: Person = getPerson();
let name1: string = person.name; // <<< Error here

I get an error like the following:

TS2322: Type 'string | undefined' is not assignable to type 'string'.
Type 'undefined' is not assignable to type 'string'.

How do I get around this error?

3

21 Answers 21

988

You can now use the non-null assertion operator that is here exactly for your use case.

It tells TypeScript that even though something looks like it could be null, it can trust you that it's not:

let name1:string = person.name!; 
//                            ^ note the exclamation mark here  
14
  • 24
    Until ESLint does not restrict usage of non-null assertion operator :)
    – Anatoly
    Commented Dec 9, 2019 at 14:36
  • 122
    This is terrible advice! Instead, help the compiler figuring it out. Add a check before accessing the property, where you either throw or return if the name is not set.
    – geon
    Commented Feb 13, 2020 at 12:56
  • 22
    Disagree with these last two comments...E.g. I'm conditionally rendering a visual element if the property exists, and attached to that element is a function which uses the property. An extra non-null check for the property inside the function is unnecessary
    – eazy_g
    Commented Sep 22, 2020 at 20:09
  • 2
    @eazy_g sounds like you are doing something wrong, if you properly validate the null previously, you wouldn't have to non-null check.
    – Rahly
    Commented Mar 13, 2021 at 16:16
  • 8
    This operator is very helpful when loading environment variables. For example my .env file has a value that I can trust will always be there, but using my env variable inside a third party Configuration Object that is expecting a String will always throw this error. In this case, the solution above was perfect.
    – Adam
    Commented Apr 10, 2021 at 4:26
296

Another way, besides yannick's answer, to use ! is to cast it as string thus telling TypeScript: I am sure this is a string, thus converting it.

let name1:string = person.name; // Error here compile time

to

let name1 = person.name as string; // No error here compile time, runtime though

This will make the error go away, but if by any chance this is not a string you will get a run-time error... which is one of the reasons we are using TypeScript to ensure that the type matches and avoid such errors at compile time.

6
  • 5
    This might make the error go away, but it never changes the actual type of the variable.
    – Manny
    Commented Jun 30, 2020 at 3:29
  • 2
    I am sorry but i think you are quite mistaken there, as i have mentioned this will in fact change the type as it will throw a runtime exception if the type doesnt happen to be a string or fails to have a toString method. This will cast whatever is there into a string, i.e you have a number it will attempt to call the .toString function of whatever you have there.
    – Harry
    Commented Jul 5, 2020 at 16:52
  • 3
    But that won't be the correct way right, say if my name has number "0" in it, it would just covert it to string... Rather it should throw an error and say invalid name. Personally, I would not use this typecast and specially check for the type passed in the object .. but we all have different coding styles, open for more discussion on the same
    – Manny
    Commented Jul 5, 2020 at 17:08
  • 4
    No need for workarounds, just one check before you try to access the variable. Both cases would be handled easily, errors would go away and your code will be safer. Just try it out :)
    – Manny
    Commented Jul 6, 2020 at 8:01
  • 2
    You can use as string but the output won't be any different from person.name! (playground). You might get a runtime error, but it will not happen at assignment as this post suggests. Both this and the top solutions are bad practices; you should check name is defined or use ??/|| to coalesce to a default value. Only do this if you know for certain name is defined (like in the OP question).
    – Connor Low
    Commented Sep 15, 2022 at 14:40
210

To avoid the compilation error I used

let name1:string = person.name || '';

And then validate the empty string.

4
  • 2
    What if it isn't a string but object?
    – Anatoly
    Commented Dec 9, 2019 at 14:37
  • 4
    If it an object then instead of ' ' it will be {}
    – Kriti
    Commented Apr 9, 2020 at 21:50
  • This is something when you can do when you are chaining values and you get this sort of error Optional chain expressions can return undefined by design - using a non-null assertion is unsafe and wrong.
    – tHeSiD
    Commented Jul 23, 2020 at 21:48
  • 3
    This is not a solution for given problem. The compiler is there to help spotting potential problems whereas an assignment like person.name || '' is just a delegation of the problem to somewhere else.
    – blelump
    Commented Feb 18, 2021 at 10:26
82

As of TypeScript 3.7 you can use nullish coalescing operator ??. You can think of this feature as a way to “fall back” to a default value when dealing with null or undefined

let name1:string = person.name ?? '';

The ?? operator can replace uses of || when trying to use a default value and can be used when dealing with booleans, numbers, etc. where || cannot be used.

As of TypeScript 4 you can use ??= assignment operator as a ??= b which is an alternative to a = a ?? b;

59

By your definition, Person.name can be null, but name1 cannot. There are two scenarios:

Person.name is never null

It tells the compiler you’re sure the name is not null by using !

let name1: string = person.name!;

Person.name can be null

Specify a default value in case name is null

let name1: string = person.name ?? "default name";
1
  • 1
    Best answer by far here. Precise and offering multiple solutions.
    – dbice
    Commented Mar 11, 2024 at 19:58
31

A more production-ready way to handle this is to actually ensure that name is present. Assuming this is a minimal example of a larger project that a group of people are involved with, you don't know how getPerson will change in the future.

if (!person.name) {
    throw new Error("Unexpected error: Missing name");
}

let name1: string = person.name;

Alternatively, you can type name1 as string | undefined, and handle cases of undefined further down. However, it's typically better to handle unexpected errors earlier on.

You can also let TypeScript infer the type by omitting the explicit type: let name1 = person.name This will still prevent name1 from being reassigned as a number, for example.

2
  • 3
    This is the most complete answer, not just a work-around that could lead to issues later down the line. Thank you!
    – dingo
    Commented Sep 16, 2022 at 9:18
  • 2
    @dingo thank you! The question asked "how do I get around this error?" but that is not the right mindset.
    – goldins
    Commented Sep 17, 2022 at 13:27
15

Here's a quick way to get what is happening:

When you did the following:

name? : string

You were saying to TypeScript it was optional. Nevertheless, when you did:

let name1 : string = person.name; //<<<Error here 

You did not leave it a choice. You needed to have a Union on it reflecting the undefined type:

let name1 : string | undefined = person.name; //<<<No error here 

Using your answer, I was able to sketch out the following which is basically, an Interface, a Class and an Object. I find this approach simpler, never mind if you don't.

// Interface
interface iPerson {
    fname? : string,
    age? : number,
    gender? : string,
    occupation? : string,
    get_person?: any
}

// Class Object
class Person implements iPerson {
    fname? : string;
    age? : number;
    gender? : string;
    occupation? : string;
    get_person?: any = function () {
        return this.fname;
    }
}

// Object literal
const person1 : Person = {
    fname : 'Steve',
    age : 8,
    gender : 'Male',
    occupation : 'IT'  
}

const p_name: string | undefined = person1.fname;

// Object instance 
const person2: Person = new Person();
person2.fname = 'Steve';
person2.age = 8;
person2.gender = 'Male';
person2.occupation = 'IT';

// Accessing the object literal (person1) and instance (person2)
console.log('person1 : ', p_name);
console.log('person2 : ', person2.get_person());
12

Try to find out what the actual value is beforehand. If person has a valid name, assign it to name1, else assign undefined.

let name1: string = (person.name) ? person.name : undefined;
6
  • 5
    hmmm. That will be too verbose if I want to do multiple assignments. Is there a way to turn off this particular warning?
    – asdasd
    Commented Feb 2, 2019 at 18:59
  • unfortunately not except you make name mandatory by removing the question mark.
    – user6749601
    Commented Feb 2, 2019 at 19:02
  • PS: Which version of TypeScript do you use? I use 3.2.4 and do not run into this Message. In my test class the code perectly compiles.
    – user6749601
    Commented Feb 2, 2019 at 19:08
  • same version. I get this error in webstorm but not in vscode
    – asdasd
    Commented Feb 2, 2019 at 19:15
  • That is strange, because I use IntelliJ which is supposed to be similar to WebStorm when it comes to application of TS-rules?!
    – user6749601
    Commented Feb 2, 2019 at 19:22
6

Solution 1: Remove the explicit type definition

Since getPerson already returns a Person with a name, we can use the inferred type.

function getPerson(){
  let person = {name:"John"};
  return person;
}

let person = getPerson();

If we were to define person: Person we would lose a piece of information. We know getPerson returns an object with a non-optional property called name, but describing it as Person would bring the optionality back.

Solution 2: Use a more precise definition

type Require<T, K extends keyof T> = T & {
  [P in K]-?: T[P]
};

function getPerson() {
  let person = {name:"John"};
  return person;
}

let person: Require<Person, 'name'> = getPerson();
let name1:string = person.name;

Solution 3: Redesign your interface

A shape in which all properties are optional is called a weak type and usually is an indicator of bad design. If we were to make name a required property, your problem goes away.

interface Person {
  name:string,
  age?:string,
  gender?:string,
  occupation?:string,
}
5

You can do it like this!

let name1:string = `${person.name}`;

But remember name1 can be an empty string.

2
  • 1
    This is the only answer that worked for me. I'm trying to do <div style={{color:this.props.color}}>text</div>. This is so weird. Commented Nov 23, 2021 at 18:39
  • This solution is the only one works for me. The other high up answers do not work. So strange. Commented Feb 19, 2024 at 6:13
4

You trying to set the variable name1, which is a type set as a strict string (it must be a string) with a value from the object field name, whose value is type set as an optional string (it can be a string or undefined, because of the question sign). If you really need this behavior, you have to change the type of name1 like this:

let name1: string | undefined = person.name;

And it'll be OK;

4

If you want to have a nullable property, change your interface to this:

interface Person {
  name?:string | null,
  age?:string | null,
  gender?:string | null,
  occupation?:string | null,
}

If being undefined is not the case, you can remove the question marks (?) from it in front of the property names.

3
  • Hi, I know its been a while. What if this interface is a parameter to a function. How do i pass the parameter. Would just passing something like this work { } , Thanks in advance for the help. Commented Feb 22, 2022 at 15:17
  • @vigneshasokan: An interface type cannot be passed as a parameter.
    – Hadi R.
    Commented Feb 22, 2022 at 19:27
  • not work. got more error Property ''data-ad-layout'' of type 'string | null | undefined' is not assignable to 'string' index type 'string' Commented Apr 6, 2024 at 7:24
3

You can use the NonNullable Utility Type:

Example

type T0 = NonNullable<string | number | undefined>;  // string | number
type T1 = NonNullable<string[] | null | undefined>;  // string[]

Docs.

3

This was the only solution I found to check if an attribute is undefined that does not generate warnings:

type NotUndefined<T, K extends keyof T> = T & Record<K, Exclude<T[K], undefined>>;
function checkIfKeyIsDefined<T, K extends keyof T>(item: T, key: K): item is NotUndefined<T, K> {
    return typeof item === 'object' && item !== null && typeof item[key] !== 'undefined';
}

Usage:

interface Listing {
    order?: string
    ...
}

obj = {..., order: 'pizza'} as Listing
if(checkIfKeyIsDefined(item: obj, 'order')) {
    order.toUpperCase() // No undefined warning O.O
}

Original answer.

2

If you remove the <Person> casting from your getPerson function, then TypeScript will be smart enough to detect that you return an object which definitely has a name property.

So just turn:

interface Person {
  name?: string,
  age?: string,
  gender?: string,
  occupation?: string,
}

function getPerson() {
  let person = <Person>{name: 'John'};
  return person;
}

let person: Person = getPerson();
let name1: string = person.name;

Into:

interface Person {
  name?: string,
  age?: string,
  gender?: string,
  occupation?: string,
}

function getPerson() {
  let person = {name: 'John'};
  return person;
}

let person = getPerson();
let name1: string = person.name;

If you cannot do that, then you will have to use the "definite assignment assertion operator" as @yannick1976 suggested:

let name1: string = person.name!;
1
  • 1
    What's the point of having an interface and not using it for typing?
    – Grinfish
    Commented Dec 3, 2021 at 23:41
2

Adding condition using ternary operator. The purpose of this line is to assign name1 the value of person.name if it is defined, and an empty string if not.

interface Person {
  name?: string,
  age?: string,
  gender?: string,
  occupation?: string,
}

function getPerson() {
  let person = <Person>{ name: "John" };
  return person;
}

let person: Person = getPerson();
let name1: string = person.name ? person.name : ''; // Added condition
1
1

I think to use Require, as mentioned by Karol Majewski, is quite nice. Another way to achieve the same would be to use intersection types (which is actually used internally by Require):

function getPerson(): Person & {name: string} {
  const person = {name:"John"};
  return person;
}

const person = getPerson();
const name1: string = person.name;

The advantage of using Require or intersection types is that we don't overrule the TypeScript compiler as it happens for the non-null assertion operator.

1

You could tighten up your types. Your getPerson function says it returns a Person, which implies that every property in the resulting value may be optional/undefined. But given the context of getPerson, we can make a stronger statement: the resulting value certainly has a name property! I would refactor getPerson to be:

function getPerson() {
  return { name: 'John' } as <Person & { name: string }>;
}

Now getPerson().name will be typed as string, not undefined | string.

You could go even further, and remove the typing altogether:

function getPerson() { return { name: 'John' }; }

TypeScript will infer the type from the returned value, and again, getPerson().name registers as type string.

0

One thing that was happening to me in my particular situation (but it is not related with the specific problem asked here) was that I was importing the wrong type in my file, given that the type was called exactly the same, but defining different properties.

Once I imported the correct type, all the issues disappeared.

0

Please try the below example. This worked for me.

interface Listing {
  name: string | undefined ,
}

export class Listing {
  public listings: Listing[] = []

  constructor(){}
  ngOnInit(){
    this.listings = fakeListing;
  }
}

Update the HTML file as the below example with &&:

  <p>First Name: {{listing.name && listing.name.split(" ")[0] }}</p>
-14

I had the same issue.

I find out that react-scrips adds "strict": true to tsconfig.json.

After I removed it, everything worked great.

I need to warn that changing this property means that you:

not being warned about potential run-time errors anymore.

as been pointed out by PaulG in comments! Thank you :)

Use "strict": false only if you fully understand what it affects!

3
  • 10
    This may 'work'. But it only works in the sense that you're not being warned about potential run time errors anymore. I'd encourage research on what the strict checking in Typescript does : medium.com/webhint/going-strict-with-typescript-be3f3f7e3295
    – PaulG
    Commented Jun 10, 2019 at 9:04
  • 7
    I should mention that the OP asked, "How do I get around this error?" not, "How do I fix this issue?"
    – devinbost
    Commented Jun 17, 2019 at 21:01
  • Simply adding name:string = undefined (as recommended by packages like json2typescript) will throw error 2322 unless you turn strict off, which is what most people will need to do. This accepted answer is clear, correct, and deserves many more votes.
    – AUSTX_RJL
    Commented Jul 5, 2019 at 18:42

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.