Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Classes with data hydration / dehydration in typescript

Tags:

I would like to share TS classes or interfaces between a React + TS frontend and node + TS backend. The problem is that TS types are stripped away in compile time, so I cannot use them when I want to convert a class instance into a JSON.

I was wondering if there are any solutions with which I could describe my object in a static file, generate the TS classes, and use this file for data hydration and dehydration as well. Some properties are moment.js objects and Decimal.js objects. I am looking for a solution where the conversion can be done based on the static descriptor, I don't need to write it for every property manually.

The dehydrated format is used in HTTP request plus it's stored in the DB (Firebase Firestore) and accessed directly by the frontend for reading.

like image 535
fodma1 Avatar asked Jun 25 '19 13:06

fodma1


1 Answers

I would approach this using the awesome io-ts library.

The dehydrated format is used in HTTP request plus it's stored in the DB (Firebase Firestore) and accessed directly by the frontend for reading.

In general, I'd assume different serialization formats for different layers. If in your specific case this simplification can be made, then 👍

I was wondering if there are any solutions with which I could describe my object in a static file

With io-ts you'd define a set of codecs as TypeScript values representing your domain. A codec is both a validator and a (de)serializer, so you can very well serialize a class instance to a JSON string, and vice-versa (provided the JSON is successfully validated and a class is then instantiated with the appropriate deserialized values).

A simplified and very custom codec performing such work follows:

// class definition:

class MyModel {
  constructor(readonly value: number) {}

  getValue() {
    return this.value;
  }
}

// codec definition:

import * as t from 'io-ts';
import { JSONFromString } from 'io-ts-types/lib/JSON/JSONFromString';

const MyModelFromString = new t.Type<MyModel, string, unknown>(
  'MyModel',
  (value): value is MyModel => value instanceof MyModel,
  str =>
    t.string
      .decode(str)
      .chain(JSONFromString.decode)
      .chain(json => t.type({ value: t.number }).decode(json))
      .map(({ value }) => new MyModel(value)),
  instance => JSON.stringify({ value: instance.value })
);

// usage:

MyModelFromString.decode('{ "value": 1 }').fold(
  errors => {
    console.error(errors);
  },
  inst => {
    // inst has type `MyModel` here
    console.log(inst.getValue());
  }
);

MyModelFromString.encode(new MyModel(2)); // '{ "value": 2 }'

On top of this, you would typically take care of serializing also a tag of some sort allowing you to decide which class you are going to try to instantiate just looking at the plain serialized JSON string.

You'll also probably want to have a look at io-ts-types which already includes many ready-to-use codecs that you can probably reuse, such as DateFromISOString.

like image 199
Giovanni Gonzaga Avatar answered Oct 12 '22 23:10

Giovanni Gonzaga