Skip to content

Commit

Permalink
test(json-mapper): fix onSerialize/onDeserialize option
Browse files Browse the repository at this point in the history
  • Loading branch information
Romakita committed Sep 13, 2023
1 parent 89c58f0 commit 26d4f3d
Show file tree
Hide file tree
Showing 6 changed files with 245 additions and 25 deletions.
36 changes: 36 additions & 0 deletions packages/specs/json-mapper/src/domain/JsonDeserializer.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import faker from "@faker-js/faker";
import {QueryParams} from "@tsed/common";
import {cleanObject, getValue} from "@tsed/core";
import {
AdditionalProperties,
CollectionOf,
Default,
DiscriminatorKey,
DiscriminatorValue,
Email,
Expand All @@ -24,12 +26,14 @@ import {
Property,
Required
} from "@tsed/schema";
import {snakeCase} from "change-case";
import {Post} from "../../test/helpers/Post";
import {User} from "../../test/helpers/User";
import "../components/DateMapper";
import "../components/PrimitiveMapper";
import "../components/SymbolMapper";
import {OnDeserialize} from "../decorators/onDeserialize";
import {OnSerialize} from "../decorators/onSerialize";
import {JsonDeserializer} from "./JsonDeserializer";

const deserializer = new JsonDeserializer();
Expand Down Expand Up @@ -586,6 +590,38 @@ describe("deserialize()", () => {
]
});
});
it("should transform object to class (ignore props)", () => {
class AvailableDatesParams {
@Ignore()
locationId: number;

@Name("start_date")
startDate: string;

@Name("end_date")
endDate: string;

@Ignore((value, ctx) => {
return !ctx.endpoint;
})
@OnDeserialize((value) => value + 1)
careCode: number;
}

const result = deserialize(
{
locationId: 5427,
careCode: 39699,
startDate: "20220606",
endDate: "20220707"
},
{type: AvailableDatesParams, useAlias: false}
);
expect(result).toEqual({
startDate: "20220606",
endDate: "20220707"
});
});
});
describe("Array<Model>", () => {
it("should transform object to class (additionalProperties = false)", () => {
Expand Down
17 changes: 12 additions & 5 deletions packages/specs/json-mapper/src/domain/JsonDeserializer.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import {classOf, getValue, isArray, isClass, isCollection, isEmpty, isNil, isPrimitive, nameOf, objectKeys, Type} from "@tsed/core";
import {classOf, getValue, isArray, isBoolean, isClass, isEmpty, isNil, nameOf, objectKeys, Type} from "@tsed/core";
import {getPropertiesStores, JsonClassStore, JsonEntityStore, JsonParameterStore, JsonPropertyStore} from "@tsed/schema";
import {add} from "husky";
import {alterAfterDeserialize} from "../hooks/alterAfterDeserialize";
import {alterBeforeDeserialize} from "../hooks/alterBeforeDeserialize";
import {alterOnDeserialize} from "../hooks/alterOnDeserialize";
import {getObjectProperties} from "../utils/getObjectProperties";
import {JsonDeserializerOptions} from "./JsonDeserializerOptions";
import {CachedJsonMapper, JsonMapperCompiler} from "./JsonMapperCompiler";
import {JsonMapperSettings} from "./JsonMapperSettings";
Expand Down Expand Up @@ -151,6 +149,7 @@ export class JsonDeserializer extends JsonMapperCompiler<JsonDeserializerOptions
properties.add(String(propertyStore.parent.schema.getAliasOf(key) || key));

if (
(propertyStore.schema?.$ignore && isBoolean(propertyStore.schema?.$ignore)) ||
propertyStore.isGetterOnly() ||
(propertyStore.schema?.$hooks?.has("groups") && this.alterGroups(propertyStore.schema, groups))
) {
Expand Down Expand Up @@ -203,18 +202,26 @@ export class JsonDeserializer extends JsonMapperCompiler<JsonDeserializerOptions
let writer = new Writer().add(`// Map ${key} ${id} ${groups || ""}`);
const pick = key !== aliasKey ? `options.useAlias ? '${aliasKey}' : '${key}'` : `'${key}'`;

const ifWriter = writer.set(`let ${key}`, `input[${pick}]`).if(`${key} !== undefined`);
// ignore hook (deprecated)
if (propertyStore.schema?.$hooks?.has("ignore")) {
this.schemes[schemaId] = propertyStore.schema;

writer = writer.if(`!alterIgnore('${schemaId}', {...options, self: input})`);
}

// pre hook
const hasDeserializer = propertyStore.schema?.$hooks?.has("onDeserialize");
let getter = `input[${pick}]`;

if (hasDeserializer) {
this.schemes[schemaId] = propertyStore.schema;
const opts = Writer.options(formatOpts);

ifWriter.set(key, `alterValue('${schemaId}', ${key}, ${opts})`);
getter = `alterValue('${schemaId}', input[${pick}], ${opts})`;
}

const ifWriter = writer.set(`let ${key}`, getter).if(`${key} !== undefined`);

const fill = this.getPropertyFiller(propertyStore, id, groups, formatOpts);

if (hasDeserializer) {
Expand Down
182 changes: 180 additions & 2 deletions packages/specs/json-mapper/src/domain/JsonSerializer.spec.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,30 @@
import {isBoolean, isObjectID} from "@tsed/core";
import {cleanObject, isBoolean, isNumber, isObjectID, useDecorators} from "@tsed/core";
import {
AdditionalProperties,
Allow,
CollectionOf,
Default,
DiscriminatorKey,
DiscriminatorValue,
Email,
Groups,
Ignore,
JsonHookContext,
MinLength,
Name,
Nullable,
Property,
Required
Required,
Uri
} from "@tsed/schema";
import {snakeCase} from "change-case";
import {parse} from "querystring";
import {Post} from "../../test/helpers/Post";
import {User} from "../../test/helpers/User";
import "../components/DateMapper";
import "../components/PrimitiveMapper";
import "../components/SymbolMapper";
import {OnDeserialize} from "../decorators/onDeserialize";
import {OnSerialize} from "../decorators/onSerialize";
import {deserialize} from "../utils/deserialize";
import {getJsonMapperTypes} from "./JsonMapperTypesContainer";
Expand All @@ -31,6 +37,7 @@ const serialize = (...args: any[]) => (serializer.map as any)(...args);
function createMap(value: any) {
return new Map([["test", value]]);
}

class ObjectId {
_bsontype = true;

Expand Down Expand Up @@ -798,6 +805,7 @@ describe("JsonSerializer", () => {
@Groups("creation")
password: string;
}

class TestProfile {
@OnSerialize((value, ctx) => {
if (isObjectID(value)) {
Expand Down Expand Up @@ -978,6 +986,176 @@ describe("JsonSerializer", () => {
renamed: "myname"
});
});
it("should serialize model to object (with default value - no assigned)", () => {
class SpaCareCategory {
@Required()
@Groups("!details")
id: string;

@Required()
label: string;

@Required()
@OnDeserialize((name: string) => snakeCase(name).toUpperCase())
code: string;

@Required()
@Default(0)
@OnSerialize((o) => {
return o || 0;
})
weight: number = 0;

constructor({id, label, code, weight}: Partial<SpaCareCategory> = {}) {
Object.assign(
this,
cleanObject({
id,
label,
code,
weight
})
);
}
}

expect(
serialize(
{
label: "categoryLabel",
code: "CATEGORY_CODE"
},
{type: SpaCareCategory}
)
).toEqual({
code: "CATEGORY_CODE",
label: "categoryLabel",
weight: 0
});
});
it("should serialize model to object (with default value - no assigned - custom decorator)", () => {
function AllowEmpty() {
return useDecorators(
Default(""),
Allow(""),
OnDeserialize((o: any) => (o === null || o === undefined ? "" : o)),
OnSerialize((o: any) => (o === null || o === undefined ? "" : o))
);
}

class SpaInformation {
@Required()
id: number;

@Required()
@AllowEmpty()
label: string = "";

@Required()
@AllowEmpty()
description: string = "";

@AllowEmpty()
currency: string = "EUR";

@Email()
@AllowEmpty()
email: string;

@AllowEmpty()
phone: string;

@Required()
@Name("are_children_accepted")
areChildrenAccepted: boolean = false;

@AllowEmpty()
website: string = "";

@Uri()
@AllowEmpty()
logo: string;

@Uri()
@AllowEmpty()
image: string;

@AllowEmpty()
location: string = "";

@Name("cancellation_hours_limit")
@Nullable(Number)
@OnSerialize((o) => (isNumber(o) ? o : null))
cancellationHoursLimit: number | null = null;

constructor({
id,
label,
description,
currency,
email,
phone,
areChildrenAccepted,
website,
logo,
image,
location,
cancellationHoursLimit = null
}: Partial<SpaInformation> = {}) {
Object.assign(
this,
cleanObject({
id,
label,
description,
currency,
email,
phone,
areChildrenAccepted,
website,
logo,
image,
location,
cancellationHoursLimit
})
);
}
}

const result = serialize(
{
id: 453,
address: null,
label: null,
currency: null,
description: null,
email: null,
phone: undefined,
areChildrenAccepted: true,
website: "website",
logo: undefined,
image: null,
cares: [],
cancellationHoursLimit: undefined
},
{type: SpaInformation}
);

expect(result).toEqual({
are_children_accepted: true,
currency: "",
description: "",
email: "",
id: 453,
image: "",
label: "",
location: "",
logo: "",
phone: "",
website: "website",
cancellation_hours_limit: null
});
});
});
describe("custom date mapper", () => {
it("should use a custom date mapper", () => {
Expand Down
18 changes: 11 additions & 7 deletions packages/specs/json-mapper/src/domain/JsonSerializer.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
classOf,
hasJsonMethod,
isBoolean,
isClassObject,
isCollection,
isDate,
Expand Down Expand Up @@ -85,7 +86,10 @@ export class JsonSerializer extends JsonMapperCompiler<JsonSerializerOptions> {
...schemaProperties.flatMap((propertyStore) => {
properties.add(propertyStore.propertyKey as string);

if (propertyStore.schema?.$hooks?.has("groups") && this.alterGroups(propertyStore.schema, groups)) {
if (
(propertyStore.schema?.$ignore && isBoolean(propertyStore.schema?.$ignore)) ||
(propertyStore.schema?.$hooks?.has("groups") && this.alterGroups(propertyStore.schema, groups))
) {
return;
}

Expand Down Expand Up @@ -124,6 +128,8 @@ export class JsonSerializer extends JsonMapperCompiler<JsonSerializerOptions> {
const key = String(propertyStore.propertyKey);
const aliasKey: string = String(propertyStore.parent.schema.getAliasOf(key) || key);
const schemaId = this.getSchemaId(id, key);
const format = propertyStore.itemSchema.get("format");
const formatOpts = format && `options: {format: '${format}'}`;

let writer = new Writer().add(`// Map ${key} ${id} ${groups || ""}`);

Expand All @@ -134,21 +140,19 @@ export class JsonSerializer extends JsonMapperCompiler<JsonSerializerOptions> {
writer = writer.if(`!alterIgnore('${schemaId}', {...options, self: input})`);
}

writer = writer.add(`let ${key} = input.${key};`).if(`${key} !== undefined`);

const format = propertyStore.itemSchema.get("format");
const formatOpts = format && `options: {format: '${format}'}`;

// pre hook
const hasSerializer = propertyStore.schema?.$hooks?.has("onSerialize");
let getter = `input.${key}`;

if (hasSerializer) {
this.schemes[schemaId] = propertyStore.schema;
const opts = Writer.options(formatOpts);

writer.set(key, `alterValue('${schemaId}', ${key}, ${opts})`);
getter = `alterValue('${schemaId}', input.${key}, ${opts})`;
}

writer = writer.set(`let ${key}`, getter).if(`${key} !== undefined`);

const fill = this.getPropertyFiller(propertyStore, key, groups, formatOpts);

if (hasSerializer) {
Expand Down
Loading

0 comments on commit 26d4f3d

Please sign in to comment.