2

I'm programming an rest api with node js and typescript and for create user, my api recieve a json post :

import {Request, Response, Router} from "express";
import {User} from '../../../models/user.model';
import {createUser} from '../../../factories/user.factory';

export default [
        {
            path: "/api/v1/user/create",
            method: "post",
            handler: [
                async (req: Request, res: Response) => {
                    createUser(new User(req.body.user));
                    res.status(200).send(req.body);
                }
            ]
        }
    ];

For exemple, I send that :

{
    "user": {
        "email": "[email protected]",
        "password": "12345678",
        "firstName": "Jérémy"
        }
    }

I would like create an object "User" with the object req.body.user :

import {Timestamp} from './timestamp.model';

export class User {
    id: bigint | undefined;
    email: string | undefined;
    password: string | undefined;
    firstName: string | undefined;
    lastName: string | undefined;
    pseudo: string | undefined;
    birthDate: Timestamp | undefined;
    lastEditDate: Timestamp | undefined;
    creationDate: Timestamp | undefined;
    googleAuthToken: string | undefined;
    language: string | undefined;
    profileAvatarUrl: string | undefined;
    profileBannerUrl: string | undefined;
    primaryLightColor: string | undefined;
    secondaryLightColor: string | undefined;
    primaryDarkColor: string | undefined;
    secondaryDarkColor: string | undefined;

    constructor(array: object) {
        console.log(array);
        // @ts-ignore
        console.log(array.gg);
        // @ts-ignore
        this.id = array.id;
        // @ts-ignore
        this.email = array.email;
        // @ts-ignore
        this.password = array.password;
        // @ts-ignore
        this.firstName = array.firstName;
        // @ts-ignore
        this.lastName = array.lastName;
        // @ts-ignore
        this.pseudo = array.pseudo;
        // @ts-ignore
        this.birthDate = array.birthDate;
        // @ts-ignore
        this.lastEditDate = array.lastEditDate;
        // @ts-ignore
        this.creationDate = array.creationDate;
        // @ts-ignore
        this.googleAuthToken = array.googleAuthToken;
        // @ts-ignore
        this.language = array.language;
        // @ts-ignore
        this.profileAvatarUrl = array.profileAvatarUrl;
        // @ts-ignore
        this.profileBannerUrl = array.profileBannerUrl;
        // @ts-ignore
        this.primaryLightColor = array.primaryLightColor;
        // @ts-ignore
        this.secondaryLightColor = array.secondaryLightColor;
        // @ts-ignore
        this.primaryDarkColor = array.primaryDarkColor;
        // @ts-ignore
        this.secondaryDarkColor = array.secondaryDarkColor;
        // @ts-ignore
    }

     toMap() {
        return {
            "id": this.id,
            "email": this.email,
            "firstName": this.firstName,
            "lastName": this.lastName,
            "pseudo": this.pseudo,
            "profileAvatarUrl": this.profileAvatarUrl,
            "birthDate": this.birthDate,
            "lastEditDate": this.lastEditDate,
            "creationDate": this.creationDate,
            "language": this.language,
            "googleAuthToken": this.googleAuthToken,
            "profileBannerUrl": this.profileBannerUrl,
            "primaryLightColor": this.primaryLightColor,
            "secondaryLightColor": this.secondaryLightColor,
            "primaryDarkColor": this.primaryDarkColor,
            "secondaryDarkColor": this.secondaryDarkColor,
        }
    }

}

I have put all this "// @ts-ignore" because if not , I've this error :

src/models/user.model.ts(27,25): error TS2339: Property 'id' does not exist on type 'object'. src/models/user.model.ts(28,32): error TS2339: Property 'email' does not exist on type 'object'. src/models/user.model.ts(29,35): error TS2339: Property 'password' does not exist on type 'object'. src/models/user.model.ts(30,36): error TS2339: Property 'firstName' does not exist on type 'object'. src/models/user.model.ts(31,35): error TS2339: Property 'lastName' does not exist on type 'object'.

My question is : How correctly make my class user for not have to put all this "// @ts-ignore" ?

Thank's in advance. Jérémy.

2
  • Why have you specified array: object? Surely you're expecting it to be something else here? Commented May 1, 2019 at 14:02
  • I've test with Array<string> and Map<string, any> but with that I cant access to my values event with ts_ignore. And i've add that to my code : console.log(typeof req.body.user); and that print object. Commented May 1, 2019 at 14:07

3 Answers 3

12

I have a different suggestion which I find nice in typescript and started using intensively. Instead of creating a class for your user you can define it as an interface.

export interface User { 
 email: string, 
 password: string, 
 firstName: string,
 lastName: string, 
 // etc 
}

and then simply do:

const user = req.body.user as User; 

It's faster and cleaner to type as long as you use these just for creating domain model objects with no business logic.

EDIT:

IF you need to stick with class then try using any type.

 export class user { 
 constructor(userDto: any) { 
   // your logic
   } 
 }


 new User(req.body.user); 
Sign up to request clarification or add additional context in comments.

7 Comments

Thank's, I now this soluce, but I've to put functions in my class user like : user.toMap(); and for that, I've to have a class :
On the type assertion in the first part of your reply, it is worth pointing out that this only tells the compiler that the input is a User. It doesn't enforce anything about the input, you are still wide open to runtime errors in the event that your payload does not conform to a User type. In other words, you still need to validate that the input actually is of type User.
Is there a way to do this where it only selects the fields that match the interface/class? I like how concise it is, but don't want it to grab other fields from the request body. Just the ones that match properties on the class.
@ontek Is there any way to fix this so that it will throw an error if req.body doesnt confirm to the type?
@lordvcs not really. To conceptualize why this is, you have to consider that anything that isn't JavaScript doesn't make it into the compiled output. In other words, interfaces are for the compiler, not the runtime. As far as reusing the interface for validation, you'd need to use something like this github.com/gristlabs/ts-interface-checker (haven't used it YMMV). Something that does seem promising is this github.com/joiful-ts/joiful, but that's using TS generators to annotate a class and put all of your validations in one place.
|
1

I'm particularly likes Dan's solution, is clean and also is fast. But if he ToMap function is need it, you can consider use https://lodash.com/ is a very handy library, helps with arrays, object mappings, deep cloning .

Regards

PD: you also can use indexer secondaryDarkColor =array.['secondaryDarkColor']

Comments

0

There are a few things that might be worth clearing up that are clouding the solution to this one.

First thing is that your toMap method is somewhat unnecessary, as an ES6 class is a template for creating an object, so creating an instance of your class with the new keyword gives you back an object, and the toMap method doesn't actually return a User object even though it conforms to the type. If you want to return a User, you'd need to modify your signature to read:

toMap(): User { ... }

This notifies the compiler that you are intending to return a User, otherwise, the compiler just sees the object and infers it to be a concrete object with the properties you've defined.

Additionally, your constructor has the following signature:

constructor(array: object) { ... }

For one, calling your variable array is bound to cause confusion down the road, as to someone reading your code, they'll assume that they're working with an array, but you've typed it as object.

As far as the type errors go, you're getting them because TypeScript's object is because it is represents the non-primitive type, and actually has no properties at all so when you attempt to access one, the compiler can't access it.

With your current code, you could replace object with any and it would most likely compile, but I don't think you'd want that.

There is a much more concise method to accomplish what you're after, you can actually define your attributes and their types by setting them in the constructor, like so:

class User {
  constructor(
    public id?: string, // or private
    public email?: string
  ) {}
}

const user = new User("abc123", "[email protected]")

console.log(user)

# >>> User: { "id": "abc123", "email": "[email protected]" }

Also note that you can use a question mark to indicate that an input is optional.

If you want to accept a json object as an input instead of putting everything in your constructor, you can always define a static method on the class that accepts an any as an input and returns an instance of the class.

See fiddle for example.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.