Cast object to interface in TypeScript - object

I'm trying to make a cast in my code from the body of a request in express (using body-parser middleware) to an interface, but it's not enforcing type safety.
This is my interface:
export interface IToDoDto {
description: string;
status: boolean;
};
This is the code where I'm trying to do the cast:
#Post()
addToDo(#Response() res, #Request() req) {
const toDo: IToDoDto = <IToDoDto> req.body; // <<< cast here
this.toDoService.addToDo(toDo);
return res.status(HttpStatus.CREATED).end();
}
And finally, the service method that's being called:
public addToDo(toDo: IToDoDto): void {
toDo.id = this.idCounter;
this.todos.push(toDo);
this.idCounter++;
}
I can pass whatever arguments, even ones that don't come close to matching the interface definition, and this code will work fine. I would expect, if the cast from response body to interface is not possible, that an exception would be thrown at runtime like Java or C#.
I have read that in TypeScript casting doesn't exist, only Type Assertion, so it will only tell the compiler that an object is of type x, so... Am I wrong? What's the right way to enforce and ensure type safety?

There's no casting in javascript, so you cannot throw if "casting fails".
Typescript supports casting but that's only for compilation time, and you can do it like this:
const toDo = <IToDoDto> req.body;
// or
const toDo = req.body as IToDoDto;
You can check at runtime if the value is valid and if not throw an error, i.e.:
function isToDoDto(obj: any): obj is IToDoDto {
return typeof obj.description === "string" && typeof obj.status === "boolean";
}
#Post()
addToDo(#Response() res, #Request() req) {
if (!isToDoDto(req.body)) {
throw new Error("invalid request");
}
const toDo = req.body as IToDoDto;
this.toDoService.addToDo(toDo);
return res.status(HttpStatus.CREATED).end();
}
Edit
As #huyz pointed out, there's no need for the type assertion because isToDoDto is a type guard, so this should be enough:
if (!isToDoDto(req.body)) {
throw new Error("invalid request");
}
this.toDoService.addToDo(req.body);

Here's another way to force a type-cast even between incompatible types and interfaces where TS compiler normally complains:
export function forceCast<T>(input: any): T {
// ... do runtime checks here
// #ts-ignore <-- forces TS compiler to compile this as-is
return input;
}
Then you can use it to force cast objects to a certain type:
import { forceCast } from './forceCast';
const randomObject: any = {};
const typedObject = forceCast<IToDoDto>(randomObject);
Note that I left out the part you are supposed to do runtime checks before casting for the sake of reducing complexity. What I do in my project is compiling all my .d.ts interface files into JSON schemas and using ajv to validate in runtime.

If it helps anyone, I was having an issue where I wanted to treat an object as another type with a similar interface. I attempted the following:
Didn't pass linting
const x = new Obj(a as b);
The linter was complaining that a was missing properties that existed on b. In other words, a had some properties and methods of b, but not all. To work around this, I followed VS Code's suggestion:
Passed linting and testing
const x = new Obj(a as unknown as b);
Note that if your code attempts to call one of the properties that exists on type b that is not implemented on type a, you should realize a runtime fault.

Related

in Typescript, try...catch error object shows "Object is of type 'unknown'.ts(2571)"

router.get('/cells', async (req, res) => {
try {
const result = await fs.readFile(fullPath, { encoding: 'utf-8' });
res.send(JSON.parse(result));
} catch (err) {
if (err.code === 'ENOENT') { // Object is of type 'unknown'.ts(2571) (local var) err: unknown
await fs.writeFile(fullPath, '[]', 'utf-8');
res.send([]);
} else {
throw err;
}
}
err.code make this ts error : Object is of type 'unknown'.ts(2571)
I definitely know that err.code exists, so I want to know how to define the type(or interface) of err?
(tsc version info : My global typescript version is v4.3.5 and the above code belongs to my project which has typescript v4.1.2)
--- Edit ---
I finally know why this error happens to me.
I thought I used tsc version under 4.3.x, but it turns out I used v4.4.x.
In vscode, cmd + shift + P and search for Select Typescript version
and I actually used v4.4.3, (I mistakenly thought on version because I only check tsc version from terminal)
Thanks for sharing Youtube video,
Just very recently, Typescript has been update to make error object inside catch be unknown instead of any
which means, your code looks something like this to your compiler
catch(e: unknown) {
// your logic
}
Provide your own interface and save into another variable to avoid this error:
catch(e : unknown) {
const u = e as YourType
// your logic
}
You can still use any there, but it's not recommended.
As stated in other answers, since TypeScript 4.4, errors are automatically cast as unknown, so you can't do anything with them without type checking. Unfortunately ErrnoExceptions are not implemented as an importable class, but rather just a regular Error with additional properties plugged in. It is a type interface in #types/node, but you can't use isinstance to check against it since there's no class definition for this exact error, so checking isinstance against Error will not let you access the err.code property. That being said, you can make the compiler happy with:
OK method
try {
await fs.readFile(file);
catch (err: NodeJS.ErrnoException) {
if (err?.code === 'ENOENT') return;
else throw err;
}
The caveat here is if you simply do if (err.code)... and forget the ?, the compiler won't complain, but you could potentially get a runtime error. This is highly unlikely unless err is null/undefined, but it's still not perfectly type safe since you could forget the ? and get runtime errors in theory. The issue here is you're telling the compiler you know what the error is when in fact the error could be literally anything (which is the motivation to automatically cast errors as unknown).
You could also do catch (err: any) but you won't get any type hints for the codes and you are still subject to the same issues if you forget the use the safe accessor on the code property. There's not a particularly easy way around this since you cannot simply use safe accessor on unknown types like this: if (err?.code === 'ENOENT') return;. I'm not quite sure why and maybe they'll add this in a later realease, but either way, my favorite way to handle these fs errors is to write a typeguard helper function like so:
BEST method
function isErrnoException(e: unknown): e is NodeJS.ErrnoException {
if ('code' in (e as any)) return true;
else return false;
}
And then your catch block like this:
try {
await fs.readFile(file);
} catch (err) {
// writing err.code here after the typeguard call satisfies the compiler and is SAFE because we already checked the member exists in the guard function.
if (isErrnoException(err) && err.code === 'ENOENT') return;
else throw err;
}
This checks at least the error object is ErrnoException-like in that it has a code property. You could get more specific and test that ALL of the ErrnoException properties exist to really make sure it's an ErrnoException.
In JavaScript/TypeScript you can throw anything, not only errors. In theory it could be anything in the catch block. If you want to prevent the type error it could make sense to check if the unknown value is a system error before checking the code.
if (err instanceof SystemError && err.code === 'ENOENT') {
// file not found
}
cast using Record<string, unknown> for the type that you don't know about... so it will be:
const mysteryObject = (unknownObject as Record<string, unknown>)
this message below really helped me and resolved the issue also:
in typescript
you can add err : any
ex:
runInitValidation(_bean: any, _oldVal: any, newVal: any) {
const { validators } = this.props;
if (!validators) return;
try {
for (let i = 0; i < validators.length; i++) {
validators[i].validate(newVal);
}
} catch (err: any) {
this.errorMessage = err.message;
}
}

Trying to retrieve Data from MongoDB using Typescript

Context: Am not too experienced with TypeScript, as we don't use it at work, am attempting to just build a little portfolio piece for personal exposure.
So to start with this is my code:
import { request, Request, Response } from 'express';
import { Neighborhood as NeighborhoodType } from '../interfaces/neighborhood.interface';
import Neighborhood from '../models/neighborhood';
const fetchNeighborhoods = async (request: Request, response: Response): Promise<void> => {
try {
const neighborhoods: NeighborhoodType[] = await Neighborhood.paginate();
response.status(200).send(neighborhoods);
} catch (error) {
throw error;
}
};
Am attempting to fetch the neighborhoods from the DB, and am receiving the error Type 'PaginateResult<Neighborhood>' is missing the following properties from type 'Neighborhood[]': length, pop, push, concat, and 26 more. on this line const neighborhoods: NeighborhoodType[] = await Neighborhood.paginate();
If I remove the NeighborhoodType[] then the method will work fine. The neighborhood interface is literally an object with a string.
export interface Neighborhood extends Document {
name: string,
}
Is it an issue with MY code or is it an issue with one of the dependencies?
For anyone who encounters this issue:
The problem stems from trying to set the return type. As Mongoose will always return one document, an array of documents or an empty array (unless using onFail()) the return type can be inferred so there is no need to add NeighborhoodType[].
The PaginateResult Type is essentially the type of Array if I'm not mistaken and is expecting the Neighborhood type to have all of the array methods which it will not.

'this' implicitly has type 'any' because it does not have a type annotation.ts(2683)

userSchema.pre('save', async function(done) {
if (this.isModified('pass')) {
const hashed = await Password.toHash(this.get('pass'));
this.set('pass', hashed);
}
done();
});
I am getting the following error from "this":
'this' implicitly has type 'any' because it does not have a type
annotation.ts(2683)
I heard the problem came from the arrow key, but I am not using an arrow key for the callback function? I am still getting an error. What gives?
I am also getting an error from "done":
Parameter 'done' implicitly has an 'any' type.ts(7006)
Could it be some kind of bug with Visual Studio Code?
It's not a bug in VS Code or in TypeScript. It is simply that there is not enough information from the call for TypeScript to determine what this will be bound to when the function is ultimately executed by Mongoose.
Assuming you have a type called User, you can fix this with an explicit this type annotation.
userSchema.pre('save', async function(this: User, done) {
if (this.isModified('pass')) {
const hashed = await Password.toHash(this.get('pass'));
this.set('pass', hashed);
}
done();
});
Note that the syntax used to specify the type of this, is a special syntax in that it does not emit a parameter in the resulting JavaScript:
function(this: X) {}
compiles to
function() {}
Have you tried doing the following, i've had this same issue and I worked around like so...
// We generally explicitly provide a type to the arguments we are using...
userSchema.pre('save', async function(done: any) {
const self: any = this;
if (this.isModified('pass')) {
const hashed = await Password.toHash(self.get('pass'));
self.set('pass', hashed);
}
done();
});
From what I've come to understand, the error seeds from the typescript config which can be modified for implicit typings in order to not show the error.

FIrebase Firestore onCreate Cloud Function: Object unidentified [duplicate]

I'm building a cloud function that will use the Stripe API to process payments. This is within a firebase project. When I run firebase deploy I get the error "Object is possible 'undefined'" const existingSource = customer.sources.data.filter( (s) => s.id === source).pop();
I'm not sure how to resolve this.
Here is my xxx.ts where getorCreateCustomer exists
/** Read the stripe customer ID from firestore, or create a new one if missing */
export const getOrCreateCustomer = async(uid: string) => {
const user = await getUser(uid);
const customerId = user && user.stripeCustomerId;
//if missing customerId, create it
if (!customerId) {
return createCustomer(uid);
}
else {
return stripe.customers.retrieve(customerId);
}
}
Based on the definitions and contents of your functions, TypeScript is unable to infer the return type of getOrCreateCustomer. It is making the assumption that it could return undefined, and its strict mode is calling you out on the fact that you could be referencing a property on an undefined object, which would result in an error at runtime.
What you need to do is declare the return type to be something that can't possibly be undefined, and make sure the code in the body of the function is correct on that guarantee (otherwise you'll get a new error).
If you can't do that (but you really should do that), you might want to instead disable strict mode in your tsconfig.json file, since that is what's enforcing this level of correctness in your code.
I suggest the first option, even if you have to write more lines of code, as it's a better use of TypeScript's typing system.
What #Doug mentioned, but also you could write your logic to make sure that every part of customer.sources.data is not undefined...
ie:
const { sources } = customer
if (sources) {
const { data } = sources
if (data) {
// then filter / etc etc ...
}
}
7 months later, I figured out the best solution.
I simply wrapped the the contents of the firebase callable function in the following if/else statement. It's a bit redundant but it works.
if (!context.auth) {
// Throwing an HttpsError so that the client gets the error details.
throw new functions.https.HttpsError('failed-precondition', 'The function must be called ' +
'while authenticated.');
}
else{ ...copy function code here }
If you don't care about the authentication piece you can simply define the type of context as any.
(data, context:any)
Open tsconfig.json and add "strictNullChecks": false to angularCompilerOptions object. It worked for me.
{
...
"angularCompilerOptions": {
"strictNullChecks": false,
...
}
}

TSLint: Backbone get() called outside of owning model meaning

I am using Microsoft's tslint-microsoft-contrib tslint configuration and I am really happy with it. However there is one rule which warns me about my code. I don't understand the rule description text or how I could solve this more elegant.
[tslint] Backbone get() called outside of owning model:
this.client.get('locations') (no-backbone-get-set-outside-model)
Code:
import * as Redis from 'ioredis';
import config from './config';
export class RedisWrapper {
private client: Redis.Redis
constructor(redisUrl: string) {
this.client = new Redis(redisUrl)
}
public async getLocations(): ILocation[] {
const locationsResponse: string = await this.client.get('locations')
}
}
In this line the tslint warning pops up: const locationsResponse: string = await this.client.get('locations')
The question:
Originally I faced this issue at a different place in my project and I thought I was supposed to write wrapper methods with typedefs, but I wasn't able to make tslint happy with that either. Can someone enlighten me what this rule means and how I could solve it?
I will quote HamletDRC (from the Microsoft team) who explained the rule itself very well:
The point of the no-backbone-get-set-outside-model rule is to make
sure that you don't invoke dynamically dispatched methods that the
compiler cannot enforce correctness on. For example, the compiler will
not complain if you type route.params.get('id'),
route.params.get('ID'), route.params.get('Id') but only one of those
invocations will actually work at runtime. The design advice is to
define a statically typed "getId(): number" method on the RouteParams
object so the compiler can enforce these calls. So, in my opinion the
rule actually has found an issue in your code that you should fix (but
see my second point :) )
Source: https://github.com/Microsoft/tslint-microsoft-contrib/issues/123
In this specific case one could extend the Redis class like this:
export class RedisWrapper extends Redis {
public async getLocations(): Promise<ILocation[]> {
const response: string = await this.get('locations');
if (response == null || response.length === 0) { return []; }
return <ILocation[]>JSON.parse(response);
}
}

Resources