When we’re building an application we often need to serialize objects to JSON for storing them in a key value store (e.g. Redis) or publishing them on a queue.
While there are libraries available like class-transformer or io-ts, there's an easier way to serialize/unserialize objects to JSON.
class-transformer depends on the reflect-metadata
package. Read this blogpost to know why you should be careful with depending on this package.
io-ts requires knowledge of some functional programming concepts. While I'm a big fan of functional programming, it's not for everyone. (The library is very well designed though)
In both cases we also need to depend on an extra package.
We want to keep our dependencies as small as possible.
What none of these packages offer is a way to deal with backwards compatibility (e.g. when you've added or renamed a property of an object).
I also want something simple.
Let's say we want to serialize a user with an email value object to JSON:
class Email {
constructor(private readonly email: string) {
if (!this.email.includes("@")) {
throw new Error(`Not an email address: ${this.email}`);
}
}
asString() {
return this.email;
}
}
class User {
constructor(
private readonly id: string,
private readonly email: Email
) {
if (!this.id) {
throw new Error("User ID cannot be empty!");
}
}
private toObject() {
return {
id: this.id,
email: this.email.asString(),
}
}
serialize() {
return JSON.stringify(this.toObject());
}
static fromSerialized(serialized: string) {
const user: ReturnType<User["toObject"]> = JSON.parse(serialized);
return new User(
user.id,
new Email(user.email)
);
}
}
const user = new User("id", new Email("john.doe@acme.com"));
const json = user.serialize();
const json = await redis.get(key);
const user = User.fromSerialized(json);
- We add a
private toObject()
method that returns a plain object for theUser
(since JSON is not aware of classes). - We add a
serialize()
method that returns the plain object as a JSON string. - We add a
static unserialize(serialized: string)
method to recreate theUser
instance.JSON.parse
hasany
as return type, which results in no type checking or autocompletion. We can grab the return type of thetoObject()
method withReturnType<User["toObject"]>
to regain type checking/autocompletion. - Always guard against invalid state in your entities. This makes sure that a
User
always has an ID and a valid email address. - Look ma, no packages needed!
Let me know if this blogpost was useful! 😊
Top comments (1)
Do you know of any library which can automate this?