diff --git a/packages/specs/json-mapper/src/domain/JsonDeserializer.spec.ts b/packages/specs/json-mapper/src/domain/JsonDeserializer.spec.ts index 71353d9a6d7..644670501b3 100644 --- a/packages/specs/json-mapper/src/domain/JsonDeserializer.spec.ts +++ b/packages/specs/json-mapper/src/domain/JsonDeserializer.spec.ts @@ -1,10 +1,8 @@ import faker from "@faker-js/faker"; import {QueryParams} from "@tsed/common"; -import {cleanObject, getValue} from "@tsed/core"; import { AdditionalProperties, CollectionOf, - Default, DiscriminatorKey, DiscriminatorValue, Email, @@ -14,7 +12,6 @@ import { Groups, Ignore, In, - Integer, JsonEntityStore, JsonHookContext, JsonParameterStore, @@ -26,14 +23,12 @@ 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(); @@ -248,6 +243,54 @@ describe("deserialize()", () => { prop: [1, 2, 3, 5] }); }); + it("should transform object to class (reserved keyword)", () => { + class Role { + @Property() + enum: string; + + constructor({enum: en}: any = {}) { + this.enum = en; + } + } + + class Model { + @Property() + id: string; + + @Ignore((ignored, ctx: JsonHookContext) => ctx.api) + password: string; + + @OnDeserialize((value) => String(value) + "test") + @Name("mapped_prop") + mappedProp: string; + + @CollectionOf(Role) + roles: Map = new Map(); + } + + const result = deserialize( + { + id: "id", + password: "string", + mapped_prop: "mappedProp", + roles: { + role1: { + enum: "role" + } + }, + add: true + }, + {type: Model, additionalProperties: false} + ); + + expect(result).toBeInstanceOf(Model); + expect(result).toEqual({ + id: "id", + mappedProp: "mappedProptest", + password: "string", + roles: new Map().set("role1", new Role({enum: "role"})) + }); + }); it("should transform object to class (additionalProperties = false)", () => { class Role { @Property() diff --git a/packages/specs/json-mapper/src/domain/JsonDeserializer.ts b/packages/specs/json-mapper/src/domain/JsonDeserializer.ts index 0dcae230a7c..b0c619f0e6c 100644 --- a/packages/specs/json-mapper/src/domain/JsonDeserializer.ts +++ b/packages/specs/json-mapper/src/domain/JsonDeserializer.ts @@ -17,6 +17,10 @@ function isDeserializable(obj: any, options: JsonDeserializerOptions) { return !(isEmpty(options.type) || (options.type === Object && !options.collectionType)); } +function varKey(k: string) { + return `__${k}`; +} + function mapParamStoreOptions(store: JsonParameterStore, options: JsonDeserializerOptions) { return { ...options, @@ -220,17 +224,17 @@ export class JsonDeserializer extends JsonMapperCompiler writer.set(key, `compileAndMap(${key}, ${opts})`); + return (writer: Writer) => writer.set(varKey(key), `compileAndMap(${varKey(key)}, ${opts})`); } const type = propertyStore.itemSchema.hasDiscriminator ? propertyStore.itemSchema.discriminator().base : propertyStore.getBestType(); const nestedMapper = this.compile(type, groups); if (propertyStore.isCollection) { - return (writer: Writer) => writer.callMapper(nameOf(propertyStore.collectionType), key, `id: '${nestedMapper.id}'`, formatOpts); + return (writer: Writer) => + writer.callMapper(nameOf(propertyStore.collectionType), varKey(key), `id: '${nestedMapper.id}'`, formatOpts); } if (generics?.length) { this.schemes[schemaId] = propertyStore.schema; - return (writer: Writer) => writer.callMapper(nestedMapper.id, key, formatOpts, `generics: schemes['${schemaId}'].nestedGenerics`); + return (writer: Writer) => + writer.callMapper(nestedMapper.id, varKey(key), formatOpts, `generics: schemes['${schemaId}'].nestedGenerics`); } - return (writer: Writer) => writer.callMapper(nestedMapper.id, key, formatOpts); + return (writer: Writer) => writer.callMapper(nestedMapper.id, varKey(key), formatOpts); } private mapOptions({groups = false, useAlias = true, types, ...options}: JsonDeserializerOptions): JsonDeserializerOptions { diff --git a/packages/specs/json-mapper/src/domain/JsonSerializer.spec.ts b/packages/specs/json-mapper/src/domain/JsonSerializer.spec.ts index b7ff03ed7c6..9d2b80357f3 100644 --- a/packages/specs/json-mapper/src/domain/JsonSerializer.spec.ts +++ b/packages/specs/json-mapper/src/domain/JsonSerializer.spec.ts @@ -788,6 +788,77 @@ describe("JsonSerializer", () => { } ]); }); + it("should serialize model (protected keyword)", () => { + class Role { + @Property() + label: string; + + constructor({label}: any = {}) { + this.label = label; + } + } + + class Model { + @Property() + id: string; + + @Ignore((ignored, ctx: JsonHookContext) => ctx.api) + password: string; + + @OnSerialize((value) => String(value) + "test") + @Name("mapped_prop") + mappedProp: string; + + @Property() + enum: string; + + @CollectionOf(Role) + roles: Map = new Map(); + } + + const model = new Model(); + // @ts-ignore + model.$isMongooseModelPrototype = true; + // @ts-ignore + model["toJSON"] = (options: any) => { + const result = deserialize( + { + id: "id", + password: "hellopassword", + mappedProp: "hello", + enum: "test" + }, + {useAlias: false, type: Model} + ); + + return serialize(result, options); + }; + + expect(serialize(model, {type: Model})).toEqual({ + id: "id", + enum: "test", + mapped_prop: "hellotest", + password: "hellopassword", + roles: {} + }); + + expect(serialize(model, {api: true, useAlias: false})).toEqual({ + id: "id", + enum: "test", + mappedProp: "hellotest", + roles: {} + }); + + expect(serialize([model], {type: Model})).toEqual([ + { + id: "id", + enum: "test", + mapped_prop: "hellotest", + password: "hellopassword", + roles: {} + } + ]); + }); it("should serialize model Array", () => { class Role { @Property() diff --git a/packages/specs/json-mapper/src/domain/JsonSerializer.ts b/packages/specs/json-mapper/src/domain/JsonSerializer.ts index 6e7fc2e0edb..ee3a03d399f 100644 --- a/packages/specs/json-mapper/src/domain/JsonSerializer.ts +++ b/packages/specs/json-mapper/src/domain/JsonSerializer.ts @@ -36,6 +36,10 @@ function getBestType(type: Type, obj: any) { return type || Object; } +function varKey(k: string) { + return `__${k}`; +} + export class JsonSerializer extends JsonMapperCompiler { constructor() { super(); @@ -156,20 +160,20 @@ export class JsonSerializer extends JsonMapperCompiler { getter = `alterValue('${schemaId}', input.${key}, ${opts})`; } - writer = writer.set(`let ${key}`, getter).if(`${key} !== undefined`); + writer = writer.set(`let ${varKey(key)}`, getter).if(`${varKey(key)} !== undefined`); const fill = this.getPropertyFiller(propertyStore, key, groups, formatOpts); if (hasSerializer) { - fill(writer.if(`${key} === input.${key}`)); + fill(writer.if(`${varKey(key)} === input.${key}`)); } else { fill(writer); } if (aliasKey !== key) { - writer.set(`obj[options.useAlias ? '${aliasKey}' : '${key}']`, key); + writer.set(`obj[options.useAlias ? '${aliasKey}' : '${key}']`, varKey(key)); } else { - writer.set(`obj.${key}`, key); + writer.set(`obj.${key}`, varKey(key)); } return writer.root(); @@ -181,13 +185,14 @@ export class JsonSerializer extends JsonMapperCompiler { const nestedMapper = this.compile(type, groups); - return (writer: Writer) => writer.callMapper(nameOf(propertyStore.collectionType), key, `id: '${nestedMapper.id}'`, formatOpts); + return (writer: Writer) => + writer.callMapper(nameOf(propertyStore.collectionType), varKey(key), `id: '${nestedMapper.id}'`, formatOpts); } const type = propertyStore.getBestType(); const nestedMapper = this.compile(type, groups); - return (writer: Writer) => writer.callMapper(nestedMapper.id, key, formatOpts); + return (writer: Writer) => writer.callMapper(nestedMapper.id, varKey(key), formatOpts); } private mapPrecondition(id: string) {