Adding class-transformer decorators to domain classes

First of all, since we are going to use decorators, we need to adapt the tsconfig.json file to add support for them (actually, to remove warnings):

  1. Edit the file and set the experimentalDecorators option to true.

Next, import the decorators from class-transformer.

  1. Add the following import statement at the top of the mediaman.ts file: import {classToPlain, plainToClassFromExist, Expose, Type} from "class-transformer";.

Finally, you also need to import reflect-metadata

  1. Add the import statement, import "reflect-metadata";, at the top of the file (that is, before the class-transformer imports).
  2. Now, adapt the MediaCollection class as follows:
class MediaCollection<T extends Media> { 
    ...
@Expose() get identifier(): string { return this._identifier; } ... @Expose() get name(): string { return this._name; } ... @Expose() @Type(options => { if(options) { return (options.newObject as
MediaCollection<T>)._type; } else { throw new Error("Cannot not determine the type
because the options object is null or undefined"); } }) get collection(): ReadonlyArray<T> { return this._collection; } ... }

The @Expose() decorators instruct class-transformer to serialize the corresponding properties when converting an object to JSON. In the preceding example, class-transformer will thus serialize the identifier, the name, and the collection properties for us.

The @Type(...​) decorator is required in order to help class-transformer know how to serialize the collection. Without it, class-transformer will just convert array entries into raw objects instead of instances of the generic type (that is, Book or Movie, in our case).

If you want to know more about using @Type decorator for generics, refer to this issue: https://github.com/typestack/class-transformer/issues/14. There is also a code sample that you can check out: https://github.com/typestack/class-transformer/tree/develop/sample/sample4-generics.

Next, you need to do the same for the Media class:

abstract class Media { 
    ... 
@Expose() get identifier(): string { return this._identifier; } ... @Expose() get name(): string { return this._name; } ... @Expose() get description(): string { return this._description; } ... @Expose() get pictureLocation(): string { return this._pictureLocation; } ... @Expose() get genre(): Genre { return this._genre; } }

And the Book class:

class Book extends Media { 
    ... 
 
    @Expose() 
    get author(): string { 
        return this._author; 
    } 
 
    ... 
 
    @Expose() 
    @Type(() => Number) 
    get numberOfPages(): number { 
        return this._numberOfPages; 
    } 
} 
 
class Movie extends Media { 
    ... 
 
    @Expose() 
    get director(): string { 
        return this._director; 
    } 
 
    ... 
 
    @Expose() 
    get duration(): string { 
        return this._duration; 
    } 
} 

With this done, class-transformer knows everything it needs: it will expose all of our properties properly.

The last thing that we need to take care of is actually serializing information. To do so, we need to use the classToPlain(...​) function provided by class-transformer. This function takes two arguments:

  1. The object to serialize (ideally containing the class-transformer decorators).
  2. An options object: through this object, it is possible to alter the serialization process.

The following is an example:

const serializedVersion = classToPlain(someObject, { excludePrefixes: ["_"] }); 

This example highlights how easy it is to serialize class instances using this library!

Doing the opposite transformation (from JSON back to class instances) is also doable now that we've annotated our domain model. Because we are using generics, we will need to use the plainToClassFromExist(...​) function, which will populate an existing object with the provided data.

The following is an example:

const instance = plainToClassFromExist<Something, any>(new Something(), value); 

This line of code requires a few explanations: