diff --git a/packages/specs/json-mapper/src/utils/deserialize.ts b/packages/specs/json-mapper/src/utils/deserialize.ts index 97dc9ef21d0..ccab482617a 100644 --- a/packages/specs/json-mapper/src/utils/deserialize.ts +++ b/packages/specs/json-mapper/src/utils/deserialize.ts @@ -1,5 +1,5 @@ import {isArray, isEmpty, isNil, MetadataTypes, nameOf, objectKeys, Type} from "@tsed/core"; -import {alterIgnore, getProperties, JsonEntityStore, JsonHookContext, JsonSchema} from "@tsed/schema"; +import {alterIgnore, getProperties, JsonEntityStore, JsonHookContext, JsonPropertyStore, JsonSchema} from "@tsed/schema"; import "../components"; import {JsonMapperContext} from "../domain/JsonMapperContext"; import {getJsonMapperTypes} from "../domain/JsonMapperTypesContainer"; @@ -70,6 +70,31 @@ function transformType(src: any, options: JsonDeserializerOptions(src, context); } +function mapItemOptions(propStore: JsonPropertyStore, options: JsonDeserializerOptions) { + const itemOpts: JsonDeserializerOptions = { + ...options, + store: undefined, + type: propStore.computedType + }; + + if (propStore.schema.hasGenerics) { + itemOpts.nestedGenerics = propStore.schema.nestedGenerics; + } else if (propStore.schema.isGeneric && options.nestedGenerics) { + const [genericTypes = [], ...nestedGenerics] = options.nestedGenerics; + const genericLabels = propStore.parent.schema.genericLabels || []; + + itemOpts.type = genericTypes[genericLabels.indexOf(propStore.schema.genericType)] || Object; + + if (itemOpts.type instanceof JsonSchema) { + itemOpts.type = itemOpts.type.getTarget(); + } + + itemOpts.nestedGenerics = nestedGenerics; + } + + return itemOpts; +} + /** * Transform given plain object to class. * @param src @@ -80,13 +105,14 @@ export function plainObjectToClass(src: any, options: JsonDeserializerO return src; } - const {type, store = JsonEntityStore.from(type), ...next} = options; - + const {type, store = JsonEntityStore.from(type)} = options; const propertiesMap = getProperties(store, {...options, withIgnoredProps: true}); - let keys = objectKeys(src); + let keys = new Set(objectKeys(src)); const additionalProperties = propertiesMap.size ? !!store.schema.get("additionalProperties") || options.additionalProperties : true; + src = alterBeforeDeserialize(src, store.schema, options); + const out: any = new type(src); propertiesMap.forEach((propStore) => { @@ -94,34 +120,19 @@ export function plainObjectToClass(src: any, options: JsonDeserializerO ? propStore.parent.schema.getAliasOf(propStore.propertyName) || propStore.propertyName : propStore.propertyName; - keys = keys.filter((k) => k !== key); - - next.type = propStore.computedType; + keys.delete(key); - if (alterIgnore(propStore.itemSchema, options)) { + if (alterIgnore(propStore.schema, options)) { return; } let value = alterValue(propStore.schema, src[key], {...options, self: src}); - if (propStore.schema.hasGenerics) { - next.nestedGenerics = propStore.schema.nestedGenerics; - } else if (propStore.schema.isGeneric && options.nestedGenerics) { - const [genericTypes = [], ...nestedGenerics] = options.nestedGenerics; - const genericLabels = propStore.parent.schema.genericLabels || []; - - next.type = genericTypes[genericLabels.indexOf(propStore.schema.genericType)] || Object; - - if (next.type instanceof JsonSchema) { - next.type = next.type.getTarget(); - } - - next.nestedGenerics = nestedGenerics; - } + const itemOptions = mapItemOptions(propStore, options); value = deserialize(value, { - ...next, - type: value === src[key] ? next.type : undefined, + ...itemOptions, + type: value === src[key] ? itemOptions.type : undefined, collectionType: propStore.collectionType }); diff --git a/packages/specs/json-mapper/test/integration/groups.integration.spec.ts b/packages/specs/json-mapper/test/integration/groups.integration.spec.ts index c6458ffac2c..1756941f2e9 100644 --- a/packages/specs/json-mapper/test/integration/groups.integration.spec.ts +++ b/packages/specs/json-mapper/test/integration/groups.integration.spec.ts @@ -1,4 +1,4 @@ -import {Groups, Property} from "@tsed/schema"; +import {CollectionOf, Groups, Property} from "@tsed/schema"; import {expect} from "chai"; import {deserialize, serialize} from "../../src"; @@ -14,6 +14,10 @@ class Product { @Groups("group.extended") description: string; + + @CollectionOf(String) + @Groups("creation") + tags: string[]; } describe("Groups", () => { @@ -58,7 +62,7 @@ describe("Groups", () => { "label": "label" }); }); - it("should deserialize object with groups restriction (groups.summary)", () => { + it("should deserialize object with groups restriction (groups.summary)", () => { const product = deserialize({ id: "id", label: "label", @@ -72,7 +76,7 @@ describe("Groups", () => { price: 100 }); }); - it("should deserialize object with groups restriction (group.*)", () => { + it("should deserialize object with groups restriction (group.*)", () => { const product = deserialize({ id: "id", label: "label", @@ -87,6 +91,21 @@ describe("Groups", () => { description: "description" }); }); + it("should deserialize object without array data (update)", () => { + class CustomRequest { + @CollectionOf(String) + @Groups("creation") + a?: string[]; + + @Groups("creation") + b?: string; + } + + const req = {a: 'a', b: 'b'} + + const res = deserialize(req, { type: CustomRequest, groups: ["update"] }); + expect(res).to.deep.eq({}) + }); }); describe("serialize", () => { const product = deserialize({ @@ -131,4 +150,4 @@ describe("Groups", () => { }); }); }); -}); \ No newline at end of file +});