Como executar a conversão de tipo de tempo de execução no TypeScript?




(2)

Dê uma olhada no JavaScript compilado e você verá que a asserção de tipo (conversão) desaparece porque é apenas para compilação. No momento, você está dizendo ao compilador que o objeto somejson é do tipo Person . O compilador acredita em você, mas neste caso isso não é verdade.

Portanto, esse problema é um problema de JavaScript em tempo de execução.

O principal objetivo para que isso funcione é, de alguma forma, informar ao JavaScript qual é o relacionamento entre as classes. Assim...

  1. Encontre uma maneira de descrever o relacionamento entre as classes.
  2. Crie algo para mapear automaticamente o json para classes com base nesses dados de relacionamento.

Há muitas maneiras de resolver isso, mas vou oferecer um exemplo em cima da minha cabeça. Isso deve ajudar a descrever o que precisa ser feito.

Digamos que temos esta classe:

class Person {
    name: string;
    child: Person;

    public giveName() {
        return this.name;
    }
}

E esses dados json:

{ 
    name: 'John', 
    child: {
        name: 'Sarah',
        child: {
            name: 'Jacob'
        }
    }
}

Para mapear isso automaticamente para ser instâncias de Person , precisamos informar ao JavaScript como os tipos estão relacionados. Não podemos usar as informações do tipo TypeScript, porque perderemos isso assim que forem compiladas. Uma maneira de fazer isso é ter uma propriedade estática no tipo que descreve isso. Por exemplo:

class Person {
    static relationships = {
        child: Person
    };

    name: string;
    child: Person;

    public giveName() {
        return this.name;
    }
}

Aqui está um exemplo de uma função reutilizável que lida com a criação de objetos para nós com base nesses dados de relacionamento:

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;
}

Agora, o seguinte código funcionará:

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

Playground

Idealmente , a melhor maneira seria usar algo que realmente lê o código TypeScript e cria um objeto que mantém o relacionamento entre as classes. Dessa forma, não precisamos manter os relacionamentos manualmente e nos preocupar com alterações de código. Por exemplo, neste momento, refatorar código é um pouco arriscado com essa configuração. Não tenho certeza de que algo assim exista no momento, mas é definitivamente possível.

Solução alternativa

Acabei de perceber que já respondi a uma pergunta semelhante com uma solução ligeiramente diferente (que não envolve dados aninhados). Você pode lê-lo aqui para mais algumas idéias:

JSON para instância de classe TypeScript?

https://code.i-harness.com

Atualmente, estou trabalhando em um projeto datilografado e realmente apreciando a inferência de tipo que o TypeScript traz para a tabela. No entanto - ao obter objetos de chamadas HTTP - eu posso convertê-los para o tipo desejado, obter a conclusão do código e chamar funções neles em tempo de compilação , mas isso resulta em erros no tempo de execução

Exemplo:

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'

Embora isso compile bem - e o intellisense sugira que eu use a função, ela fornece uma exceção de tempo de execução. Uma solução para isso pode ser:

var somejson = { 'name' : 'Ann' };
var ann = new Person(somejson);

console.log(ann.name);        // 'Ann'
console.log(ann.giveName());  // 'Ann'

Mas isso exigirá que eu crie construtores para todos os meus tipos. Em particular, ao lidar com tipos de árvore e / ou com coleções provenientes da chamada AJAX, seria necessário percorrer todos os itens e criar uma instância para cada um deles.

Então, minha pergunta: existe uma maneira mais elegante de fazer isso? Ou seja, convertido para um tipo e tem as funções prototípicas disponíveis para ele imediatamente?


O protótipo da classe pode ser afetado dinamicamente para o objeto:

function cast<T>(obj: any, cl: { new(...args): T }): T {
  obj.__proto__ = cl.prototype;
  return obj;
}

var john = cast(/* somejson */, Person);

Veja a documentação de __proto__ aqui .





typescript