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.
- 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 asdocument.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'.
// ~~~~
- 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.