Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to Create a Simple Typescript Metadata Annotation

I have some fields that need to be formatted before sent to server-side.

So, I would like to serialize some fields of my typescript classes using custom serializers, something like that would be ideal:

export class Person {
    @serializeWith(MyDateSerializer)
    private date: Date;
}

post(url, value) {
    this.http.post(url, JSON.stringfy(value, (key, val) => {
       if (//value has serializeWith annotation) {
           return //serialize with custom serializer
       }
    }));
}

Anything close to this would be acceptable, any help is welcome. Thanks

like image 411
Marcos J.C Kichel Avatar asked Jun 28 '16 20:06

Marcos J.C Kichel


1 Answers

My solution below is built on top of:

  1. TypeScript Decorators
  2. Metadata spec (early stage/experimental)

Prerequisites:

  1. Enable Decorator and decorator metadata support in TypeScript in you tsconfig.json or on the command line:

tsconfig.json

{
    "compilerOptions": {
        "target": "es5", // you have to target es5+
        "experimentalDecorators": true,
        "emitDecoratorMetadata": true
        // ....
    }
    // ...
}
  1. Install reflect-metadata package

    npm install --save-dev reflect-metadata

serializerWith Decorator (factory)

A decorator factory is something that takes one or many parameters and returns a decorator function that TypeScript uses in the generated JavaScript code.

I have chosen to directly pass the serialization function into my decorator factory and use the reflect-metadata implementation of the metdata spec to associate the serializer function with the property. I will later retrieve it and use it at run-time.

function serializeWith(serializer: (input: any) => string) : (target: any, propertyKey: string) => void {
    return function(target: any, propertyKey: string) {
        // serialization here is the metadata key (something like a category)
        Reflect.defineMetadata("serialization", serializer, target, propertyKey);
    }
}

Use

Given this serializer:

function MyDateSerializer(value : any) : string {
    console.log("MyDateSerializer called");
    return "dummy value";
}

We can then apply the decorator factory like this:

import "reflect-metadata"; // has to be imported before any decorator which uses it is applied

class Greeter {
    @serializeWith(MyDateSerializer)
    public greeting : string;

    constructor(message: string) {
        this.greeting = message;
    }
}

And we can get and use the serializer like this:

var greetingInstance = new Greeter("hi");

var serializerFunc : (input: any) => string = Reflect.getMetadata("serialization", greetingInstance, "greeting");

serializerFunc(greetingInstance.greeting);

Sample

main.ts

import "reflect-metadata";

function serializeWith(serializer: (input: any) => string) : (target: any, propertyKey: string) => void {
    return function(target: any, propertyKey: string) {
        console.log("serializeWith called: adding metadata");
        Reflect.defineMetadata("serialization", serializer, target, propertyKey);
    }
}


function MyDateSerializer(value : any) : string {
    console.log("MyDateSerializer called");
    return "bla";
}

class Greeter {
    @serializeWith(MyDateSerializer)
    public greeting : string;

    constructor(message: string) {
        console.log("Greeter constructor");
        this.greeting = message;
    }
}

var greetingInstance = new Greeter("hi");

var serializerFunc : (input: any) => string = Reflect.getMetadata("serialization", greetingInstance, "greeting");

var serializedValue = serializerFunc(greetingInstance.greeting);
console.log(serializedValue);

Outputs

c:\code\tmp\lll>node build\main.js
serializeWith called: adding metadata
Greeter constructor
MyDateSerializer called
bla
like image 75
Ivan Zlatev Avatar answered Sep 27 '22 19:09

Ivan Zlatev