# 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
}
})
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
}
}
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()];
}
}
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;
}
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;
}
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;
}
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;
}
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();
}
}
}
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";
}
}
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 {}
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.
Last Updated: 10/24/2024, 6:33:40 AM
Other topics
- Session & cookies
- Passport.js
- Keycloak
- Prisma
- TypeORM
- MikroORM
- Mongoose
- GraphQL
- GraphQL WS
- Apollo
- TypeGraphQL
- GraphQL Nexus
- Socket.io
- Swagger
- AJV
- Multer
- Serve static files
- Templating
- Serverless HTTP
- Seq
- OIDC
- Stripe
- Agenda
- Terminus
- Serverless
- Server-sent events
- IORedis
- Vike
- Jest
- Vitest
- Controllers
- Providers
- Model
- JsonMapper
- Middlewares
- Pipes
- Interceptors
- Authentication
- Hooks
- Exceptions
- Throw HTTP Exceptions
- Cache
- Command
- Response Filter
- Injection scopes
- Custom providers
- Lazy-loading provider
- Custom endpoint decorator
- Testing
- Customize 404