Search code examples
typescriptreflectionmetaprogramming

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.


Solution

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

    function propertyOf<TObj>(name: keyof TObj) {
        return name;
    }
    

    or

    function propertiesOf<TObj>(_obj: (TObj | undefined) = undefined) {
        return function result<T extends keyof TObj>(name: T) {
            return name;
        }
    }
    

    or using Proxy

    export function proxiedPropertiesOf<TObj>(obj?: TObj) {
        return 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<MyInterface>();
    myInterfaceProperties.myProperty;
    

    or

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