user3413621
user3413621

Reputation: 521

'compile-time' way to get all property names defined interface

I'd like to create a generic TypeScript class for rendering (as a HTML list) of an array of objects which implement a specific interface.

e.g.

class GenericListRenderer<T> {
  items: T[];

 constructor(listItems: T[], className?: string){
      this.items = listItems;
      ...
    }

    private getPropertyNames(): string[]{

    // What is the best way to access all property names defined in
    // TypeScript interface 'T' that was used in this generic?
    ...   
    }

    render(){
      var propNames: string[] = this.getPropertyNames();
      // render list with each item containing set of all 
      // key(prop name)/value pairs defined by interface 'T'  
      ...

    }
}

Q: what would be the best way to get a 'compile-time' list of all property names defined in the specified () TypeScript interface?

Like C++ templates, I believe that TypeScript could resolves these generics during "compile time", when TypeScript type information (like an interface supplied to the generic used to instantiate a particular object) is readily available.

Since all the required type information is potentially supplied, I was just curious if there was a TypeScript extension/facility available to access this info w/o excessive runtime filtering of 'vanilla' Javascript objects -- which might be problematic due to ambiguous inheritance issues (e.g. desired TypeScript inherited interface properties may get filtered out if runtime, generic, Javascript (obj.hasOwnProperty(prop)) is used for filtering properties).

This useful property introspection potential could be unambiguously resolved with TypeScript's super-set of type meta-data during 'compile-time' vs trying to resolve this information in the translated Javascript, when all of this type information is discarded.

I'd hate to 'reinvent-the wheel' with a potentially imperfect Javascript hack if a standard (TypeScript) approach exists.

Upvotes: 42

Views: 37961

Answers (5)

Ahmet Emrebas
Ahmet Emrebas

Reputation: 944

Try SWC compiler. You will be able to use Object.keys(new ClassName()). Also it is 20x faster than regular compiler

Upvotes: 0

user12425684
user12425684

Reputation: 1

You can try Accelatrix as it contains a JavaScript type introspection system that works at runtime:

https://www.nuget.org/packages/Accelatrix

https://www.npmjs.com/package/accelatrix

https://github.com/accelatrix/accelatrix

A type introspection system

The type system of JavaScript is enhanced to include the four fundamental operations:

- GetHashCode()
- GetType()
- Equals()
- ToString()

You can now deal with classes in JavaScript at runtime as you would in C#, e.g.:

var myDog = new Bio.Mammal(8);  
var myCat = new Bio.Feline(8, 9);

var timeIsSame = (new Date()).Equals(new Date());                //true           
var areEqual = myDog.Equals(myCat);                              // false           
var myCatType = myCat.GetType();                                 // Bio.Feline           
var myCatBaseType = myCat.GetType().BaseType;                    // Bio.Mammal           
var isAnimal = myCat.GetType().IsAssignableFrom(Bio.Animal);     // true           
var enums = Bio.TypesOfLocomotion.GetType();                     // Accelatrix.EnumType 

// sample classes:

export namespace Bio
{           
    export enum TypesOfLocomotion           
    {           
        Crawl,           
        Swim,           
        Walk,           
        Fly,           
    }           

    abstract class LivingBeing
    {
        public isExtinct = false;
    }

    export abstract class Eukaryotes extends LivingBeing
    {
        private locomotion: TypesOfLocomotion = null;

        public get Locomotion(): TypesOfLocomotion
        {
            return this.locomotion;
        }
        public set Locomotion(value: TypesOfLocomotion)
        {
            this.locomotion = value;
        }
    }

    export class Animal extends Eukaryotes
    {
        public isAnimal = true;

        public constructor()
        {
            super();
        }
    }
    
    export class Mammal extends Animal
    {
        private readonly numberOfTits: number;

        public constructor(numberOfTits: number)
        {
            super();
            this.numberOfTits = numberOfTits;
        }

        public get NumberOfTits(): number
        {
            return this.numberOfTits;
        }

        public SayHello(): string
        {
            return "Hello";
        }
    }

    export class Feline extends Mammal
    {
        private readonly numberOfLives: number;

        public constructor(numberOfTits: number, numberOfLives: number)
        {
            super(numberOfTits);
            this.numberOfLives = numberOfLives == null ? 9 : numberOfLives;
            this.Locomotion = TypesOfLocomotion.Walk;
        }

        public get NumberOfLives(): number
        {
            return this.numberOfLives;
        }
    }
}

Upvotes: 0

Flavien Volken
Flavien Volken

Reputation: 21349

This is not possible to retrieve that information at runtime, and they will never be per default possible unless you store them during the compilation. The solution is therefore be in reflection using ReflectDecorators.

Here is an excellent article covering the question of retrieving compilation time metadata at the runtime. In short: you add a decorator to the interface you would like to keep the description, this one will be converted to a JSON object which one will be stored into the code itself. During the runtime, you will be able to retrieve this JSON object having all the interface data. This is now experimental (11th Feb 2016) but in a good way.

Note: The reason why it will never by per default is basically a choice of design for TS not to overload the js code with metadata (unlike Dart).

Upvotes: 11

kimamula
kimamula

Reputation: 12659

This is possible by using custom transformers introduced by https://github.com/Microsoft/TypeScript/pull/13940, which is available in typescript >= 2.4.1.

My npm package, ts-transformer-keys, is a good example.

import { keys } from 'ts-transformer-keys';

interface Props {
  id: string;
  name: string;
  age: number;
}
const keysOfProps = keys<Props>();

console.log(keysOfProps); // ['id', 'name', 'age']

Upvotes: 39

Fenton
Fenton

Reputation: 251172

At runtime all of the type information is erased, so the best you can do is enumerate the properties of one of the objects. This will give you back all properties, even those that were not on the specified interface.

class GenericListRenderer<T> {

    constructor(private items: T[], private className?: string){

    }

    private getPropertyNames(): string[] {
        var properties: string[] = [];

        if (this.items.length > 0) {
            for (var propertyName in this.items[0]) {
                console.log(propertyName);
                properties.push(propertyName);
            }
        }

        return properties;
    }

    render(){
      var propNames: string[] = this.getPropertyNames();
    }

}

class Example {
    constructor(public name: string) {

    }
}

var example = new Example('Steve');

var a = new GenericListRenderer<Example>([example]);

a.render();

There is also Object.keys(), which gives you back all of the properties, although it is only supported in IE9 and above.

If you can supply more of a use case for what you want to do with the properties, it may be possible to give you an alternate solution using attributes or some other mechanism.

Upvotes: 1

Related Questions