objects - ¿Cómo hacer la conversión de tipos en tiempo de ejecución en TypeScript?
typescript list of objects (3)
Actualmente estoy trabajando en un proyecto de mecanografía y realmente estoy disfrutando de la inferencia de tipos que TypeScript trae a la mesa. Sin embargo, cuando obtengo objetos de llamadas HTTP, puedo convertirlos en el tipo deseado, obtener la finalización del código y llamar a las funciones en el tiempo de compilación , pero eso genera errores en el tiempo de ejecución
Ejemplo:
class Person{
name: string;
public giveName() {
return this.name;
}
constructor(json: any) {
this.name = json.name;
}
}
var somejson = { ''name'' : ''John'' }; // Typically from AJAX call
var john = <Person>(somejson); // The cast
console.log(john.name); // ''John''
console.log(john.giveName()); // ''undefined is not a function''
Aunque esto se compila muy bien, e intellisense me sugiere que use la función, ofrece una excepción en tiempo de ejecución. Una solución para esto podría ser:
var somejson = { ''name'' : ''Ann'' };
var ann = new Person(somejson);
console.log(ann.name); // ''Ann''
console.log(ann.giveName()); // ''Ann''
Pero eso requerirá que cree constructores para todos mis tipos. En particular, cuando se trata con tipos de árbol y / o con colecciones provenientes de la llamada AJAX, uno tendría que recorrer todos los elementos y actualizar una instancia para cada uno.
Entonces mi pregunta: ¿hay una manera más elegante de hacer esto? Es decir, ¿convertir a un tipo y tener las funciones prototípicas disponibles de inmediato?
Eche un vistazo al JavaScript compilado y verá que la aserción de tipo (conversión) desaparece porque es solo para compilar.
Ahora mismo le está diciendo al compilador que el objeto
somejson
es del tipo
Person
.
El compilador te cree, pero en este caso eso no es cierto.
Entonces, este problema es un problema de JavaScript en tiempo de ejecución.
El objetivo principal para que esto funcione es decirle de alguna manera a JavaScript cuál es la relación entre las clases. Asi que...
- Encuentre una manera de describir la relación entre clases.
- Cree algo para asignar automáticamente el json a las clases en función de estos datos de relación.
Hay muchas formas de resolverlo, pero ofreceré un ejemplo fuera de mi cabeza. Esto debería ayudar a describir lo que hay que hacer.
Digamos que tenemos esta clase:
class Person {
name: string;
child: Person;
public giveName() {
return this.name;
}
}
Y estos datos json:
{
name: ''John'',
child: {
name: ''Sarah'',
child: {
name: ''Jacob''
}
}
}
Para asignar esto automáticamente a instancias de
Person
, necesitamos decirle al JavaScript cómo se relacionan los tipos.
No podemos usar la información de tipo TypeScript porque la perderemos una vez que se haya compilado.
Una forma de hacerlo es tener una propiedad estática en el tipo que describe esto.
Por ejemplo:
class Person {
static relationships = {
child: Person
};
name: string;
child: Person;
public giveName() {
return this.name;
}
}
Luego, aquí hay un ejemplo de una función reutilizable que se encarga de crear los objetos para nosotros en función de estos datos de relación:
function createInstanceFromJson<T>(objType: { new(): T; }, json: any) {
const newObj = new objType();
const relationships = objType["relationships"] || {};
for (const prop in json) {
if (json.hasOwnProperty(prop)) {
if (newObj[prop] == null) {
if (relationships[prop] == null) {
newObj[prop] = json[prop];
}
else {
newObj[prop] = createInstanceFromJson(relationships[prop], json[prop]);
}
}
else {
console.warn(`Property ${prop} not set because it already existed on the object.`);
}
}
}
return newObj;
}
Ahora funcionará el siguiente código:
const someJson = {
name: ''John'',
child: {
name: ''Sarah'',
child: {
name: ''Jacob''
}
}
};
const person = createInstanceFromJson(Person, someJson);
console.log(person.giveName()); // John
console.log(person.child.giveName()); // Sarah
console.log(person.child.child.giveName()); // Jacob
Idealmente , la mejor manera sería usar algo que realmente lea el código TypeScript y cree un objeto que mantenga la relación entre las clases. De esa manera, no necesitamos mantener manualmente las relaciones y preocuparnos por los cambios de código. Por ejemplo, en este momento la refactorización del código es un poco arriesgado con esta configuración. No estoy seguro de que algo así exista en este momento, pero definitivamente es posible.
Solución alternativa
Me acabo de dar cuenta de que ya respondí una pregunta similar con una solución ligeramente diferente (aunque eso no involucra datos anidados). Puedes leerlo aquí para obtener más ideas:
El prototipo de la clase puede verse afectado dinámicamente al objeto:
function cast<T>(obj: any, cl: { new(...args): T }): T {
obj.__proto__ = cl.prototype;
return obj;
}
var john = cast(/* somejson */, Person);
Puede usar Object.assign, por ejemplo:
var somejson = { ''name'' : ''Ann'' };
var ann = Object.assign(new Person, somejson);
console.log(ann.name); // ''Ann''
console.log(ann.giveName()); // ''Ann''
Pero si tiene clases anidadas, debe mapear el objeto y asignarlo para cada elemento.