Home > Net >  Explicitly tell typescript that a variable is not null
Explicitly tell typescript that a variable is not null

Time:05-15

I'm having an issue with typescript. I have a mongoose model that has an interface casted on it.

import { Schema, model } from "mongoose";

interface IUser {
    userId: string
    guildId: string
    cash: number
    bank: number
}

const userSchema = new Schema<IUser>({
    userId: { type: String, required: true },
    guildId: { type: String, required: true },
    cash: { type: Number, required: true },
    bank: { type: Number, required: true }
});

const User = model<IUser>("User", userSchema);

export default User;

Now, whenever I import it, find a document from the collection and try to access a property like userDocument.cash typescript raises an error: Object is possibly 'null' like in the snippet below:

import UserModel from "path/to/model";
// some code (not relevant)
if (!(UserModel.findOne({ userId: user.id, guildId: i.guildId }))) newUser(i.guild!, user); // It checks if an user document exists and if not it creates one. (user.id and i.guildId are defined)
const userDocument = await UserModel.findOne({ userId: user.id, guildId: i.guildId });
console.log(userDocument.cash) // typescript error: Object is possibly 'null'
//          ~~~~~~~~~~~~

Here, we obviously know that the document exists and we could tell it to typescript with the ! operator (so it would be: console.log(userDocument!.cash)). The issue is this doesn't scale very well and I'd have to add the ! each time I would like to use the userDocument. So my question is: is there a way to explcitly tell typescript that the variable is not null?

So far I've found two workarounds but none of them are satisfying.

  1. We can cast the IUser interface to the userDocument variable but then we lose access to all the other document-related properties/methods like document.save. I found casting the variable to the IUser interface to fix this but this is not a perfect sollution as I lose all the other document-related properties such as document.save():
const userDocument = await UserModel.findOne({ userId: user.id, guildId: i.guildId }) as IUser;
userDocument.cash = 300;
userDocument.save() // typescript error: Property 'save' does not exist on type 'IUser'.
//           ~~~~
  1. The other one works perfectly fine but it's just far too ugly for me to use it:
let userDocument = await UserModel.findOne({ userId: user.id, guildId: i.guildId });
userDocument = userDocument!
userDocument.cash = 300;
userDocument.save() // works

I have read this post but I don't see any way to translate its solution to my problem.

CodePudding user response:

You can add ! to the await expression itself. That way you don't have to reassign the variable like in your attempt #2.

const userDocument = (await UserModel.findOne({ userId: user.id, guildId: i.guildId }))!;

Note that the await needs to be wrapped in parentheses and the ! postfix operator must be outside it due to operator precedence rules.

  • Related