I have some TypeScript classes with fields that are Map objects containing other objects. I'm using the class-transformer package to serialize and deserialize (to and from JSON), but I'm having trouble getting it to work.
Additionally, a field in the parent class is not being correctly transformed into its intended type during deserialization (see the "created" field which is NOT transformed into Date
).
The documentation for class-transformer seems either outdated or lacks guidance for handling this specific scenario.
Here is a examples of the classes:
// Type to be used for Unique Identifiers (UID).
export type Uid = string;
export class UidObject {
// Unique identifier of the object.
readonly uid: Uid;
constructor(uid: Uid) {
this.uid = uid;
}
}
export class TimeStampedObject extends UidObject {
@Type(() => Date)
readonly created: Date;
constructor(uid: Uid, created: Date) {
super(uid);
this.created = created;
}
}
export class Profile extends TimeStampedObject {
/// User display alias.
readonly alias: string;
constructor(
uid: Uid,
created: Date,
alias: string,
) {
super(uid, created);
this.alias = alias;
}
}
export class Group extends UidObject {
readonly name: string;
@Type(() => Map) // What should be used here?
readonly profiles: Map<string, Profile>;
constructor(
uid: Uid,
name: string,
profiles: Map<string, Profile>,
) {
super(uid);
this.name = name;
this.profiles = profiles;
}
}
Using the "class-transformer" functions does not seem to convert the "profiles" field into a Map<Uid, Profile>
but just to Map<string, any>
.
let profiles = new Map();
profiles.set("uid-prof-1", new Profile("uid-prof-1", new Date(), "alias-prof-1"));
profiles.set("uid-prof-2", new Profile("uid-prof-2", new Date(), "alias-prof-2"));
let group = new Group("uid-coco", "Coco", profiles);
let json = instanceToPlain<Group>(group);
let instance = plainToInstance<Group, any>(Group, json);
console.log(group);
console.log("------ After =>");
console.log(instance); // This shows an objets which is not in the correct format.
I implemented a solution based on this old workaround by creating a custom decorator to handle the transformation of nested Map objects.
export function MapTransform<V>(cls: ClassConstructor<V>): PropertyDecorator {
return Transform(({ value }: TransformFnParams) => {
const map = new Map<string, V>();
for (const [key, val] of Object.entries(value)) {
map.set(key, plainToInstance(cls, val));
}
return map;
}, { toClassOnly: true });
}
And apply the @MapTransform
decorator to the profiles
field in your Group
class:
export class Group extends UidObject {
readonly name: string;
@MapTransform(Profile) // Use the custom decorator here
readonly profiles: Map<string, Profile>;
constructor(
uid: Uid,
name: string,
profiles: Map<string, Profile>,
) {
super(uid);
this.name = name;
this.profiles = profiles;
}
}