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.
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 codec
s 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
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With