Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Safe way to extract property names

I'm looking for a way to get an object property name with typechecking that allows to catch possible regressions after refactoring.

Here's an example: the component where I have to pass the property names as strings and it will be broken if I'll try to change the property names in the model.

interface User {    name: string;    email: string; }  class View extends React.Component<any, User> {     constructor() {       super();       this.state = { name: "name", email: "email" };    }     private onChange = (e: React.FormEvent) => {       let target = e.target as HTMLInputElement;       this.state[target.id] = target.value;       this.setState(this.state);    }     public render() {       return (          <form>             <input                id={"name"}                value={this.state.name}                onChange={this.onChange}/>             <input                id={"email"}                value={this.state.email}                onChange={this.onChange}/>             <input type="submit" value="Send" />          </form>       );    } } 

I'd appreciate if there's any nice solution to solve this issue.

like image 695
shadeglare Avatar asked Nov 05 '15 14:11

shadeglare


People also ask

How do you get the properties name of an object in typescript?

To dynamically access an object's property: Use keyof typeof obj as the type of the dynamic key, e.g. type ObjectKey = keyof typeof obj; . Use bracket notation to access the object's property, e.g. obj[myVar] .

What are property names?

Noun. Definition: Identifies the property name that is displayed on the Property List; an easily recognizable or descriptive name; typically a street address or property name (e.g. “100 Main Street” or “Commerce Center Mall'). The default name is the Loan Name.


2 Answers

In TS 2.1 the keyof keyword was introduced which made this possible:

const propertyOf = <TObj>(name: keyof TObj) => name; 

or

const propertiesOf = <TObj>(_obj: (TObj | undefined) = undefined) => <T extends keyof TObj>(name: T): T => name; 

or using Proxy

export const proxiedPropertiesOf = <TObj>(obj?: TObj) =>   new Proxy({}, {     get: (_, prop) => prop,     set: () => {       throw Error('Set not supported');     },   }) as {     [P in keyof TObj]?: P;   }; 

These can then be used like this:

propertyOf<MyInterface>("myProperty"); 

or

const myInterfaceProperties = propertiesOf<MyInterface>(); myInterfaceProperties("myProperty"); 

or

const myInterfaceProperties = propertiesOf(myObj); myInterfaceProperties("myProperty"); 

or

const myInterfaceProperties = proxiedPropertiesOf(myObj); myInterfaceProperties.myProperty; 

This will give an error if myProperty is not a property of the type MyObj.

like image 88
nzjoel Avatar answered Oct 26 '22 15:10

nzjoel


Right now there's not really a great way of doing this, but there are currently some open suggestions on github (See #1579, #394, and #1003).

What you can do, is what's shown in this answer—wrap referencing the property in a function, convert the function to a string, then extract the property name out of the string.

Here's a function to do that:

function getPropertyName(propertyFunction: Function) {     return /\.([^\.;]+);?\s*\}$/.exec(propertyFunction.toString())[1]; } 

Then use it like so:

// nameProperty will hold "name" const nameProperty = getPropertyName(() => this.state.name); 

This might not work depending on how the code is minified so just watch out for that.

Update

It's safer to do this at compile time. I wrote ts-nameof so this is possible:

nameof<User>(s => s.name); 

Compiles to:

"name"; 
like image 37
David Sherret Avatar answered Oct 26 '22 13:10

David Sherret