# JsonMapper

The @tsed/json-mapper package is responsible to map a plain object to a model and a model to a plain object.

It provides two functions serialize and deserialize to transform object depending on which operation you want to perform. It uses all decorators from @tsed/schema package and TypeScript metadata to work.

Ts.ED use this package to transform any input parameters sent by your consumer to a class and transform returned value by your endpoint to a plain javascript object to your consumer.

# Configuration

@Configuration({
  jsonMapper: {
    additionalProperties: false,
    disableUnsecureConstructor: false,
    strictGroups: false
  }
})
1
2
3
4
5
6
7

# jsonMapper.additionalProperties

Enable additional properties on model. By default, false. Enable this option is dangerous and may be a potential security issue.

# jsonMapper.disableUnsecureConstructor

Pass the plain object to the model constructor. By default, true.

It may be a potential security issue if you have as constructor with this followings code:

class MyModel {
  constructor(obj: any = {}) {
    Object.assign(this, obj); // potential prototype pollution
  }
}
1
2
3
4
5

# jsonMapper.strictGroups

Enable strict mode for @Groups decorator. By default, false. See Groups for more information.

WARNING

The strictGroups option is enabled by default in the next major version of Ts.ED.

# Usage

JsonMapper works with a class and decorators. Use decorators on properties to describe a model and use this model as an input parameter or return value by your endpoint. Here is a model example:

    Note

    Take a look on Jest/Mocha tabs to see serialize and deserialize functions usage.

    Now we can use the Person model on a controller:

    import {BodyParams} from "@tsed/platform-params";
    import {Get, Post, Returns} from "@tsed/schema";
    import {Controller} from "@tsed/di";
    import {Person} from "../models/Person";
    
    @Controller("/")
    export class PersonsCtrl {
      @Post("/")
      @Returns(200, Person)
      async save1(@BodyParams() person: Person): Promise<Person> {
        console.log(person instanceof Person); // true
    
        return person; // will be serialized according to your annotation on Person class.
      }
    
      // OR
      @Post("/")
      @Returns(200, Person)
      async save2(@BodyParams("person") person: Person): Promise<Person> {
        console.log(person instanceof Person); // true
    
        return person; // will be serialized according to your annotation on Person class.
      }
    
      @Get("/")
      @Returns(200, Array).Of(Person) // Add the correct json schema for swagger essentially.
      async getPersons(): Promise<Person[]> {
        return [new Person()];
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30

    Note

    In the previous example, we can see Returns decorator usage. In all case, Ts.ED infer the returned value and apply the correct transformation on your response.

    Returns decorator is used to generate the correct swagger documentation only.

    WARNING

    When a model is provided, JsonMapper will follow exactly the JsonSchema generated by @tsed/schema package.

    It means, if you missed decorating one or more properties on your model, these properties won't be appear after the transformation.

      Note: Result is displayed in Jest/Mocha tabs.

      # Ignore properties (deprecated)

      deprecated

      This decorator is deprecated. Use Groups decorator instead of.

      # Usage

      Ignore decorator can be used to ignore explicitly a property when a transformation have been performed.

      For example, you have a base model to create a User named UserCreation where the password is required, but you don't want to expose this field in other cases. One of the solution is to use class inheritance to solve this problem.

        # With a callback

        Ignore decorator since v6.13.0 accept a callback which will be called when a property have been serialized or deserialized. The callback will give you more control over the way to ignore a property.

        class User {
          @Name("id")
          _id: string;
        
          @Property()
          firstName: string;
        
          @Property()
          lastName: string;
        
          @Ignore((value, ctx) => ctx.endpoint) // should not serialized when the object is returned by an endpoint.
          password: string;
        
          @Ignore((value, ctx) => ctx.mongoose) // should be serialized when the object is returned by an endpoint.
          scopes: string[];
        
          @Ignore()
          alwaysIgnored: string;
        }
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19

        Here is the available options on ctx:

        Prop Type Description
        endpoint boolean It's an endpoint context
        mongoose boolean It's a mongoose context

        # Additional properties

        AdditionalProperties decorator can be used to accept any additional properties on a specific model.

          # Alias

          Name decorator lets you to rename the exposed property in your json schema.

          For example mongo db uses the _id property. In order not to give any indication to our consumer about the nature of the database, it's better to rename the property to id.

          import {Description, Example, Name} from "@tsed/schema";
          import {ObjectID} from "@tsed/mongoose";
          
          export class Model {
            @Name("id")
            @Description("Object ID")
            @Example("5ce7ad3028890bd71749d477")
            _id: string;
          }
          
          // same example with mongoose
          export class Model2 {
            @ObjectID("id")
            _id: string;
          }
          
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15

          # OnSerialize

          OnSerialize decorator can be used to intercept and change the property value when a serialization is performed on class.

          import {OnSerialize} from "@tsed/schema";
          
          export class Person {
            @OnSerialize((v) => v + "Test")
            property: string;
          }
          
          1
          2
          3
          4
          5
          6

          # OnDeserialize

          OnDeserialize decorator can be used to intercept and change the property value when a deserialization is performed on class.

          import {OnDeserialize} from "@tsed/schema";
          
          export class Person {
            @OnDeserialize((v) => v + "Test")
            property: string;
          }
          
          1
          2
          3
          4
          5
          6

          # Type mapper

          @tsed/json-mapper use classes to transform an input value to the expected value:

          Type Mapper
          Primitives PrimitiveMapper ,
          Symbol SymbolMapper ,
          Objects DateMapper ,

          It's possible to add your own type mapper by using the JsonMapper decorator on a class. Just copy a mapper implementation and import the mapper in your application.

          # Primitives

          PrimitiveMapper is responsible to map the primitive value like Boolean, Number or String.

            # Cheat sheet

            Input Type Output
            1 String "1"
            "1" String "1"
            null Number null
            "null" Number null
            "1" Number 1
            1 Number 1
            "to1" Number Throw Bad Request. This is the only case where JsonMapper throw a cast type error.
            true Boolean true
            "true" Boolean true
            "1" Boolean true
            1 Boolean true
            false Boolean false
            "false" Boolean false
            "0" Boolean false
            0 Boolean false
            "" Boolean false
            "null" Boolean null
            undefined Boolean undefined

            # Symbol

            SymbolMapper is responsible to map a String to Symbol or a Symbol to a String.

              # Date

              DateMapper is responsible to map a Number, String to a Date or a Date to a String.

                WARNING

                Ts.ED doesn't transform Date to date format or hours format because it depends on each project guidelines.

                But you can easily implement a Date mapper for each format with the Date API or moment:

                import {isBoolean} from "@tsed/core";
                import {DateFormat} from "@tsed/schema";
                import {serialize, JsonMapper, JsonMapperContext, JsonMapperMethods} from "../../src/index";
                
                @JsonMapper(Date)
                export class DateMapper implements JsonMapperMethods {
                  deserialize(data: string | number, ctx: JsonMapperContext): Date;
                  deserialize(data: boolean | null | undefined, ctx: JsonMapperContext): boolean | null | undefined;
                  deserialize(data: any, ctx: JsonMapperContext): any {
                    // don't convert unexpected data. In normal case, Ajv reject unexpected data.
                    // But by default, we have to skip data deserialization and let user to apply
                    // the right mapping
                    if (isBoolean(data) || data === null || data === undefined) {
                      return data;
                    }
                
                    return new Date(data);
                  }
                
                  serialize(object: Date, ctx: JsonMapperContext): any {
                    const date = new Date(object);
                
                    switch (ctx.options.format) {
                      case "date":
                        const y = date.getUTCFullYear();
                        const m = ("0" + (date.getUTCMonth() + 1)).slice(-2);
                        const d = ("0" + date.getUTCDate()).slice(-2);
                
                        return `${y}-${m}-${d}`;
                      default:
                        return new Date(object).toISOString();
                    }
                  }
                }
                
                1
                2
                3
                4
                5
                6
                7
                8
                9
                10
                11
                12
                13
                14
                15
                16
                17
                18
                19
                20
                21
                22
                23
                24
                25
                26
                27
                28
                29
                30
                31
                32
                33
                34

                # Create your own type mapper

                It's possible de to change add your own type mapper by using the JsonMapper decorator on a class. Just copy a mapper implementation and import the mapper in your application.

                A mapper must declare the type it must work on and implement two methods: serialize and deserialize.

                import {JsonMapper, JsonMapperMethods, JsonMapperCtx} from "@tsed/json-mapper";
                
                @JsonMapper(String)
                export class TheTypeMapper implements JsonMapperMethods {
                  deserialize(data: any, ctx: JsonMapperCtx): String {
                    return JSON.stringify(data) + ":deserialize";
                  }
                
                  serialize(data: any, ctx: JsonMapperCtx): String {
                    return JSON.stringify(data) + ":serialize";
                  }
                }
                
                1
                2
                3
                4
                5
                6
                7
                8
                9
                10
                11
                12

                Then import your new mapper in your Server.ts as following:

                import {Configuration} from "@tsed/di";
                
                import "./mappers/TheTypeMapper";
                
                @Configuration({
                  mount: {
                    "/rest": []
                  }
                })
                export class Server {}
                
                1
                2
                3
                4
                5
                6
                7
                8
                9
                10

                # Moment

                Moment.js (opens new window) is a powerful library to transform any formatted date string to a Moment instance.

                You can change the Date mapper behavior to transform string to a Moment instance.

                  
                  
                  1

                  Last Updated: 6/13/2024, 12:50:58 PM

                  Other topics