1

I have a Typescript app. I use the localstorage for development purpose to store my objects and I have the problem at the deserialization.

I have an object meeting of type MeetingModel:

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

I store this object in the localStorage using JSON.stringify(meeting).

I suppose that stringify call moment.toJson(), that returns an iso string, hence the value stored is: {"date":"2016-12-26T15:03:54.586Z"}.

When I retrieve this object, I do:

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

The problem is: meeting.date contains a string instead of a moment !

So, first I'm wondering why TypeScript let this happen ? Why can I assign a string value instead of a Moment and the compiler agree ?

Second, how can I restore my objects from plain JSON objects (aka strings) into Typescript types ?

I can create a factory of course, but when my object database will grow up it will be a pain in the *** to do all this work.

Maybe there is a solution for better storing in the local storage in the first place?

Thank you

Vivien Adnot
  • 1,157
  • 3
  • 14
  • 30
  • Have you considered using IndexedDB? –  Jan 07 '17 at 15:49
  • 2
    its not really typescript issue..its `json.parse`..[check here](http://stackoverflow.com/questions/4511705/how-to-parse-json-to-receive-a-date-object-in-javascript#14509447) – Suraj Rao Jan 07 '17 at 15:49
  • @torazaburo no, what are the benefits of IndexedDB regarding my problem ? – Vivien Adnot Jan 07 '17 at 16:06
  • @suraj Thanks ! It answers my 2nd question ! I will fix my code asap. Any idea regarding my first question ? – Vivien Adnot Jan 07 '17 at 16:06
  • IndexedDB stores actual JS objects, and is keyed/indexed for efficiency. However, you will still have to rehydrate your objects somehow. –  Jan 07 '17 at 16:14
  • Why typescript allows `const meeting: MeetingModel = JSON.parse(...)`? Because `JSON.parse()` is declared as returning `any`, and `any` is a special type which is compatible with anything. – artem Jan 07 '17 at 18:35

2 Answers2

5

1) TypeScript is optionally typed. That means there are ways around the strictness of the type system. The any type allows you to do dynamic typing. This can come in very handy if you know what you are doing, but of course you can also shoot yourself in the foot.

This code will compile:

var x: string = <any> 1;

What is happening here is that the number 1 is casted to any, which to TypeScript means it will just assume you as a developer know what it is and how you to use it. Since the any type is then assigned to a string TypeScript is absolutely fine with it, even though you are likely to get errors during run-time, just like when you make a mistake when coding JavaScript.

Of course this is by design. TypeScript types only exist during compile time. What kind of string you put in JSON.parse is unknowable to TypeScript, because the input string only exists during run-time and can be anything. Hence the any type. TypeScript does offer so-called type guards. Type guards are bits of code that are understood during compile-time as well as run-time, but that is beyond the scope of your question (Google it if you're interested).

2) Serializing and deserializing data is usually not as simple as calling JSON.stringify and JSON.parse. Most type information is lost to JSON and typically the way you want to store objects (in memory) during run-time is very different from the way you want to store them for transfer or storage (in memory, on disk, or any other medium). For instance, during run-time you might need lookup tables, user/session state, private fields, library specific properties, while in storage you might want version numbers, timestamps, metadata, different types of normalization, etc. You can JSON.stringify anything you want in JavaScript land, but that does necessarily mean it is a good idea. You might want to design how you actually store data. For example, an iso string looks pretty, but takes a lot of bytes. If you have just a few that does not matter, but when you are transferring millions a second you might want to consider another format.

My advise to you would be to define interfaces for the objects you want to save and like moment create a .toJson method on your model object, which will return the DTO (Data Transfer Object) that you can simply serialize with JSON.stringify. Then on the way back you cast the any output of JSON.parse to your DTO and then convert it back to your model with a factory function or constructor of your creation. That might seem like a lot of boilerplate, but in my experience it is totally worth it, because now you are in control of what gets stored and that gives you a lot of flexility to change your model without getting deserialization problems.

Good luck!

Lodewijk Bogaards
  • 19,777
  • 3
  • 28
  • 52
  • Thank you for your very detailed explanation ! Now I understand everything :) The DTO concept is amazing, I will use it for sure – Vivien Adnot Jan 08 '17 at 09:55
3

You could use the reviver feature of JSON.parse to convert the string back to a moment:

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

Check browser support for reviver, though, as it's not the same as basic JSON.parse

Richard Szalay
  • 83,269
  • 19
  • 178
  • 237