Skip to content

Commit 1ee9349

Browse files
authored
fix: prevent returning class property names when using Expose() with Map (#405)
1 parent 18eb79f commit 1ee9349

File tree

2 files changed

+103
-4
lines changed

2 files changed

+103
-4
lines changed

src/TransformOperationExecutor.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ export class TransformOperationExecutor {
127127
this.recursionStack.add(value);
128128
}
129129

130-
const keys = this.getKeys(targetType as Function, value);
130+
const keys = this.getKeys(targetType as Function, value, isMap);
131131
let newValue: any = source ? source : {};
132132
if (
133133
!source &&
@@ -390,21 +390,26 @@ export class TransformOperationExecutor {
390390
return meta ? meta.reflectedType : undefined;
391391
}
392392

393-
private getKeys(target: Function, object: Record<string, any>): string[] {
393+
private getKeys(target: Function, object: Record<string, any>, isMap: boolean): string[] {
394394
// determine exclusion strategy
395395
let strategy = defaultMetadataStorage.getStrategy(target);
396396
if (strategy === 'none') strategy = this.options.strategy || 'exposeAll'; // exposeAll is default strategy
397397

398398
// get all keys that need to expose
399399
let keys: any[] = [];
400-
if (strategy === 'exposeAll') {
400+
if (strategy === 'exposeAll' || isMap) {
401401
if (object instanceof Map) {
402402
keys = Array.from(object.keys());
403403
} else {
404404
keys = Object.keys(object);
405405
}
406406
}
407407

408+
if (isMap) {
409+
// expose & exclude do not apply for map keys only to fields
410+
return keys;
411+
}
412+
408413
if (!this.options.ignoreDecorators && target) {
409414
// add all exposed to list of keys
410415
let exposedProperties = defaultMetadataStorage.getExposedProperties(target, this.transformationType);

test/functional/es6-data-types.spec.ts

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import 'reflect-metadata';
2-
import { classToPlain, plainToClass } from '../../src/index';
2+
import { classToPlain, plainToClass, Expose } from '../../src/index';
33
import { defaultMetadataStorage } from '../../src/storage';
44
import { Type } from '../../src/decorators';
55

@@ -252,4 +252,98 @@ describe('es6 data types', () => {
252252
],
253253
});
254254
});
255+
256+
it('using Map with objects with Expose', () => {
257+
defaultMetadataStorage.clear();
258+
259+
class Weapon {
260+
constructor(public model: string, public range: number) {}
261+
}
262+
263+
class User {
264+
@Expose() id: number;
265+
@Expose() name: string;
266+
@Expose()
267+
@Type(() => Weapon)
268+
weapons: Map<string, Weapon>;
269+
}
270+
271+
const plainUser = {
272+
id: 1,
273+
name: 'Max Pain',
274+
weapons: {
275+
firstWeapon: {
276+
model: 'knife',
277+
range: 1,
278+
},
279+
secondWeapon: {
280+
model: 'eagle',
281+
range: 200,
282+
},
283+
thirdWeapon: {
284+
model: 'ak-47',
285+
range: 800,
286+
},
287+
},
288+
};
289+
290+
const weapons = new Map<string, Weapon>();
291+
weapons.set('firstWeapon', new Weapon('knife', 1));
292+
weapons.set('secondWeapon', new Weapon('eagle', 200));
293+
weapons.set('thirdWeapon', new Weapon('ak-47', 800));
294+
295+
const user = new User();
296+
user.id = 1;
297+
user.name = 'Max Pain';
298+
user.weapons = weapons;
299+
const plainedUser = classToPlain(user);
300+
expect(plainedUser).not.toBeInstanceOf(User);
301+
expect(plainedUser).toEqual({
302+
id: 1,
303+
name: 'Max Pain',
304+
weapons: {
305+
firstWeapon: {
306+
model: 'knife',
307+
range: 1,
308+
},
309+
secondWeapon: {
310+
model: 'eagle',
311+
range: 200,
312+
},
313+
thirdWeapon: {
314+
model: 'ak-47',
315+
range: 800,
316+
},
317+
},
318+
});
319+
320+
function checkPlainToClassUser(classUser: User) {
321+
expect(classedUser).toBeInstanceOf(User);
322+
expect(classedUser.id).toEqual(1);
323+
expect(classedUser.name).toEqual('Max Pain');
324+
expect(classedUser.weapons).toBeInstanceOf(Map);
325+
expect(classedUser.weapons.size).toEqual(3);
326+
expect(classedUser.weapons.get('firstWeapon')).toBeInstanceOf(Weapon);
327+
expect(classedUser.weapons.get('firstWeapon')).toEqual({
328+
model: 'knife',
329+
range: 1,
330+
});
331+
expect(classedUser.weapons.get('secondWeapon')).toBeInstanceOf(Weapon);
332+
expect(classedUser.weapons.get('secondWeapon')).toEqual({
333+
model: 'eagle',
334+
range: 200,
335+
});
336+
expect(classedUser.weapons.get('thirdWeapon')).toBeInstanceOf(Weapon);
337+
expect(classedUser.weapons.get('thirdWeapon')).toEqual({
338+
model: 'ak-47',
339+
range: 800,
340+
});
341+
}
342+
343+
const classedUser = plainToClass(User, plainUser, { excludeExtraneousValues: false });
344+
checkPlainToClassUser(classedUser);
345+
346+
const classedUser2 = plainToClass(User, plainUser, { excludeExtraneousValues: true });
347+
checkPlainToClassUser(classedUser2);
348+
});
255349
});

0 commit comments

Comments
 (0)