json - Serialização/deserialização de tipos TypeScript no localstorage


Eu tenho uma aplicação em Typescript. Eu uso o localStorage para fins de desenvolvimento para armazenar os meus objetos e tenho o problema na desserialização.

Tenho um objeto de reunião do tipo MeetingModel:

export interface MeetingModel {
    date: moment.Moment; // from the library momentjs
}

Eu armazeno este objeto no localStorage usando JSON.stringify(meeting).

Suponho que a chamada stringify invoque moment.toJson(), que retorna uma string iso, portanto o valor armazenado é: {"date":"2016-12-26T15:03:54.586Z"}.

Quando recupero este objeto, eu faço:

const stored = window.localStorage.getItem("meeting");
const meeting: MeetingModel = JSON.parse(stored);

O problema é: meeting.date contém uma string em vez de um momento !

Então, primeiro estou a perguntar-me por que é que o TypeScript deixou que isto acontecesse? Por que posso atribuir um valor string em vez de um Moment e o compilador concorda?

Em segundo lugar, como é que posso restaurar os meus objetos de objetos JSON simples (ou seja, strings) para tipos TypeScript?

Posso criar uma fábrica, claro, mas quando a minha base de dados de objetos crescer será um fardo fazer todo este trabalho.

Talvez haja uma solução para armazenar melhor no armazenamento local desde o início?

Obrigado

Resposta #1

1) TypeScript é opcionalmente tipado. Isso significa que existem formas de contornar a rigidez do sistema de tipos. O tipo any permite-lhe fazer tipagem dinâmica. Isto pode ser muito útil se souber o que está a fazer, mas claro que também pode causar problemas a si próprio.

Este código irá compilar:

var x: string = <any> 1;

O que está a acontecer aqui é que o number 1 é convertido para any, o que, para o TypeScript, significa que ele vai apenas assumir que você, como programador, sabe o que é e como utilizá-lo. Uma vez que o tipo any é depois atribuído a um string, o TypeScript não tem problema nenhum com isso, mesmo que seja provável que você obtenha erros durante a execução, tal como quando comete um erro ao programar em JavaScript.

Claro que isto é intencional. Os tipos em TypeScript existem apenas durante o tempo de compilação. Que tipo de string você coloca em JSON.parse é desconhecido para o TypeScript, porque a string de entrada só existe durante o tempo de execução e pode ser qualquer coisa. Daí o tipo any. O TypeScript oferece os chamados 'type guards'. 'Type guards' são pedaços de código que são compreendidos tanto durante o tempo de compilação como durante o tempo de execução, mas isso está fora do âmbito da sua pergunta (pesquise no Google se tiver interesse).

2) Serializar e desserializar dados geralmente não é tão simples como chamar JSON.stringify e JSON.parse. A maior parte da informação de tipo é perdida para JSON e, tipicamente, a forma como se quer armazenar objetos (em memória) durante a execução é muito diferente da forma como se quer armazená-los para transferência ou armazenamento (em memória, no disco ou em qualquer outro meio). Por exemplo, durante a execução pode ser necessário ter tabelas de consulta, estado de utilizador/sessão, campos privados, propriedades específicas de bibliotecas, enquanto no armazenamento pode-se querer números de versão, carimbos de data/hora, metadados, diferentes tipos de normalização, etc. Pode JSON.stringify qualquer coisa que quiser no mundo JavaScript, mas isso não significa necessariamente que seja uma boa ideia. Pode querer planear como armazena realmente os dados. Por exemplo, uma string ISO parece bonita, mas ocupa muitos bytes. Se tiver apenas algumas, isso não importa, mas quando estiver a transferir milhões por segundo, pode querer considerar outro formato.

O meu conselho para ti seria definir interfaces para os objetos que queres guardar e, como o moment, criar um método .toJson no teu objeto de modelo, que retornará o DTO (Data Transfer Object) que podes simplesmente serializar com JSON.stringify. Depois, no caminho de volta, fazes cast do resultado any do JSON.parse para o teu DTO e, em seguida, convertes de volta para o teu modelo com uma função de fábrica ou construtor da tua autoria. Isto pode parecer muito código repetitivo, mas pela minha experiência, vale totalmente a pena, porque agora tens controlo sobre o que é armazenado, o que te dá muita flexibilidade para alterar o teu modelo sem ter problemas de desserialização.

Boa sorte!

Resposta #2

Poderia usar a funcionalidade reviver do JSON.parse para converter a string de volta para um moment:

JSON.parse(input, (key, value) => {
  if (key == "date") {
    return parseStringAsMoment(value);
  } else {
    return value;
});

Verifique o suporte do navegador para o reviver, pois não é o mesmo que o JSON.parse básico