2016-07-27 2 views
1

J'écris un unmarshaller générique. Il convertit les données de base de données de graphe en classes de modèle TypeScript (1.8.7) générées. L'entrée est JSON. La sortie doit être une instance d'une classe de modèle.
Mon but ultime est de créer quelque chose comme Hibernate OGM, uniquement pour les trames Tinkerpop et TypeScript, avec le point de terminaison REST au milieu.TypeScript - Passer une classe comme argument et réflexion

Quelle est la bonne façon de passer une classe en tant que paramètre et d'atteindre ses membres statiques? Je veux avoir quelque chose comme ceci:

SomeModel some = <SomeModel> unmarshaller.fromJSON({/*Object from JSON*/}, SomeModel); 

J'ai essayé d'écrire une méthode. Je ne sais pas si je me dirige dans la bonne direction, n'hésitez pas à suggérer différentes approches. Mais quand j'ai essayé d'exécuter ceci sur Plunker, j'ai eu des erreurs d'exécution avec stacktrace inutilement.

Le modèle superclasse ressemble à ceci:

/** 
* Things common to all Frames models on the Typescript side. 
*/ 
export class FrameModel 
{ 
    // Model metadata 
    static discriminator: string; 
    static graphPropertyMapping: { [key:string]:string; }; 
    static graphRelationMapping: { [key:string]:string; }; 

    // Each instance needs a vertex ID 
    private vertexId: number; 
    public getVertexId(): number { 
     return this.vertexId; 
    } 
} 

classe modèle Exemple:

import {TestPlanetModel} from './TestPlanetModel'; 
import {TestShipModel} from './TestShipModel'; 

export class TestGeneratorModel extends FrameModel 
{ 
    static discriminator: string = 'TestGenerator'; 
    static graphPropertyMapping: { [key:string]:string; } = { 
     bar: 'boo', 
     name: 'name', 
     rank: 'rank', 
    }; 
    static graphRelationMapping: { [key:string]:string; } = { 
     colonizes: 'colonizedPlanet', 
     commands: 'ship', 
    }; 

    boo: string; 
    name: string; 
    rank: string; 

    public colonizedPlanet: TestPlanetModel[]; // edge label 'colonizedPlanet' 

    public ship: TestShipModel; // edge label 'ship' 

} 

Je n'ai pas trouvé beaucoup de matériel sur la réflexion et la gestion de classe dactylographiée.
Je sais comment je ferais cela en Java.
Je sais comment je ferais cela en JavaScript.
Je comprends que je pourrais obtenir des résultats similaires avec les décorateurs, mais avoir des champs ou des champs statiques semblait un peu plus simple, pour les modèles générés.

+0

Son classique d'appeler 'class'' klass' :) – basarat

Répondre

0

Vous avez peut-être déjà remarqué que les membres de la classe ne peuvent pas avoir de mot-clé const. Mais vous pouvez aller avec static à la place. Aussi, le membre devrait être public si vous voulez qu'il soit accessible du monde extérieur.

public static graphPropertyMapping: { [key:string]:string; } = { 
    bar: 'boo', 
    name: 'name', 
    rank: 'rank', 
}; 

En ce qui concerne la création d'instance de résultat:

let result = new clazz(); 

//copy properties 

return result; 
+0

À droite, l'exemple ci-dessus a été écrit à la main de sorte que ces erreurs étaient contenues. –

+0

Je me demande vraiment comment TypeScript implémente et limite 'static' - car en JavaScript, rien n'est vraiment statique, c'est lié à la fonction. Donc techniquement, si vous héritez d'une fonction, ce qui est je suppose que l'héritage de classe est fait dans TS, vous héritez aussi du champ statique? Donc, techniquement, 'MyClass.staticField' pourrait-il aussi être accédé en tant que' MySubclass.staticField'? A moins que TS ne colle à la notion de 'static' de Java ... –

+0

Oui,' MyClass.staticField' peut aussi être accédé en tant que 'MySubclass.staticField'. Vous avez raison, il est lié à la fonction (définition de la classe) –

0

Si je vous comprends bien alors voici quelque chose pour vous aider à démarrer:

interface Model {} 

interface ModelData {} 

interface MyModelConstructor<M extends Model, D extends ModelData> { 
    new(data: D): M; 
    // static members 
    graphPropertyMapping: any; 
    graphRelationMapping: any; 
} 

class Unmarshaller { 
    public fromJSON<T>(input: string | ModelData, ctor: MyModelConstructor<T, ModelData>): T { 
     let data: ModelData = (typeof input === "string") ? JSON.parse(input) : input; 

     let propertyMapping = ctor.graphPropertyMapping; 
     let relationMapping = ctor.graphRelationMapping; 

     // do whatever with the mappings 

     return new ctor(input); 
    } 
} 

(code in playground)

Je don Je ne sais pas à quoi ressemblent vos modèles, alors j'espère que c'est assez proche.

+0

Cela semble raisonnable. Pourriez-vous s'il vous plaît ajouter un lien à la raison d'être de la nécessité d'une interface? Je veux dire, en Java, il n'y a pas besoin d'une interface pour inspecter les métadonnées de classe et les annotations. Est-ce une façon "officielle" ou un effet de levier de certains effets secondaires? Merci –

+0

En tapuscrit [les interfaces ont de nombreux rôles] (http://www.typescriptlang.org/docs/handbook/interfaces.html), et vous n'avez pas besoin de les utiliser, mais cela a du sens. Dans mon exemple, je n'ai pas utilisé de métadonnées ni d'annotations, la seule chose ici est l'interface qui représente une référence à une classe (par opposition à une référence à une instance): 'MyModelConstructor'. Si vous voulez être plus précis sur ce que vous voulez savoir, je vais modifier ma réponse pour y répondre. –

+0

J'ai compris votre exemple un peu plus. On dirait que vous suggérez d'avoir une classe constructeur pour chaque modèle. C'est à dire. chaque modèle aurait une classe constructeur respective. Puisque je génère les modèles, je préférerais qu'il n'y ait qu'une seule classe de service capable de construire le modèle à partir des données JSON et des métadonnées dans la classe du modèle. Je pense que je vais améliorer le code de ma question. –

0

J'ai récemment publié une version améliorée du compilateur TypeScript qui permet exactement ce que vous attendez: lire toutes les métadonnées (statiques ou non) des champs d'une classe. Par exemple, vous pouvez écrire:

interface MyInterface { 
    active:boolean; 
    description: string; 
} 

class MyClass { 
    id: number; 
    name: string; 
    myComplexField: MyInterface; 
} 

function printMembers(clazz: Class) { 
    let fields = clazz.members.filter(m => m.type.kind !== 'function'); //exclude methods. 
    for(let field of fields) { 
     let typeName = field.type.kind; 
     if(typeName === 'class' || typeName === 'interface') { 
      typeName = (<Class | Interface>field.type).name; 
     } 
     console.log(`Field ${field.name} of ${clazz.name} has type: ${typeName}`); 
    } 
} 

printMembers(MyClass.getClass()); 

c'est la sortie:

$ node main.js 
Field id of MyClass has type: number 
Field name of MyClass has type: string 
Field myComplexField of MyClass has type: MyInterface 

Bien sûr, si vous changez vous récupérerez accès à la propriété members de clazz à statics tous les membres statiques. Ces informations peuvent également être consultées au moment du codage, de sorte que vous pouvez utiliser la saisie semi-automatique. Vous pouvez faire la même chose avec les métadonnées Interfaces. Il suffit d'écrire MyInterface par exemple, et accéder à ses membres. Vous pouvez trouver le projet here.