简体   繁体   中英

Restricting the type on function argument in Node.js and TypeScript

Working on a Node.js project and using TypeScript.

I'm trying to restrict a functions argument type to a specific base class. I'm new with both Node & TypeScript and come from a C# background, so likely not quite understanding some of the characteristics of the lang.

Take these snippets.

First, my class declarations

class DTO{
}

class userDTO extends DTO{
    @IsDefined({message:"Username required"})
    @Expose()
    @Length(1,10, {message:"min 1 max 10"})
    username:String;    
  }
  
class badDTO {
    name:String;
}

Now I will create instances:

let user = new userDTO();
user.username = "My username";

let isUserDTO = user instanceof DTO; // Evaluates true

let bad = new badDTO();
bad.name = "Bob";

let isBadDTO = user instanceof DTO; // Evaluates false

Here is the signature of the method I intend to call

export default function ValidateDTO(objToValidate:DTO, validateMissingProperties:boolean): Array<string>{
    return [];
}

Finally, when I actually call the function.

let userErrors = ValidateDTO(user, true);

// Why is this allowed?
let badErr = ValidateDTO(bad, true);

I am expecting the 2nd ValidateDTO to show me a warning and not actually run because 'bad' is not a DTO as proven by instanceOf above - if i try passing a string as the 2nd arg I see an error, which is what i expected from passing a non-DTO as the first arg.

Can someone please show me where I am going wrong? How can I restrict the type of object passed into a function.

Happy to share other code as required too. Not sure what else i might be missing.

You're not at all alone being surprised by this. :-) One of the key things about the TypeScript type system is that it's structural (based on structure), not nominal (based on names). As long as something has the minimum structure necessary, it matches even if it has a different ancestry. That means any object will be accepted by the type system as your DTO type because your DTO type has no properties, so all objects match it.

That's mostly a feature , but sometimes you want to disable it. The usual approach when you want to disable it is to use a branding property:

class DTO {
    __brand = "DTO" as const;
}

Now, only objects that have a __brand property with the value "DTO" will be allowed where DTO objects are expected by the type system.


Here's a complete example with some minor changes to be more in keeping with JavaScript/TypeScript naming conventions and to supply some bits that were missing in the question code (presumably to keep it short! :-) ):

class DTO {
    __brand = "DTO" as const;
}

class UserDTO extends DTO {
    /* Commenting these out as they're not relevant to the question.
    @IsDefined({message:"Username required"})
    @Expose()
    @Length(1,10, {message:"min 1 max 10"})
    */
    username: string;
    
    constructor(username: string) {
        super();
        this.username = username;
    }
}
  
class BadDTO {
    name: string = "";
}

function validateDTO(objToValidate: DTO, validateMissingProperties: boolean): string[] {
    return [];
}

// Okay
validateDTO(new UserDTO("Joe"), true);

// Disallowed by the type system
validateDTO(new BadDTO(), false);

Playground link


Side note 2: In that example I added a constructor to UserDTO that initialized the username property. TypeScript has a shorthand for when you want to use a constructor paramter to initialize an instance property, this is functionally identical to the UserDTO in my example:

class UserDTO extends DTO {
    /* Commenting these out as they're not relevant to the question.
    @IsDefined({message:"Username required"})
    @Expose()
    @Length(1,10, {message:"min 1 max 10"})
    */
    //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− note no `username` declaration here
    
    constructor(public username: string) {
    //          ^−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− note adding `public`
        super();
        // −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− note no code here to do the
        // initialization; it's implicit in the `public` declaration above
    }
}

Which you use is a matter of style.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM