Classes with data hydration / dehydration in typescript
Asked Answered
L

1

7

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.

Languishment answered 25/6, 2019 at 13:33 Comment(3)
Have you seen io-ts?Anse
Thanks seems like pretty much what I've been looking for.Languishment
Also io-ts-types includes some commonly used codecs like e.g DateFromISOString. Also keep on mind that, if you want to (de)serialize from/to class instances, you'll have to write your own codecs (the default io-ts t.type works with plain pojos)Anse
A
5

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.

Anse answered 26/6, 2019 at 12:40 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.