Skip to content

Commit b98a2a2

Browse files
committed
Bugfix: clone internal object references
1 parent 7c0e035 commit b98a2a2

3 files changed

Lines changed: 116 additions & 37 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [8.1.1] - 2025-06-02
9+
10+
### Fixed
11+
12+
- Clone of internal object references.
13+
814
## [8.1.0] - 2025-06-01
915

1016
### Added

spec/type-manager.spec.ts

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,7 @@ describe('Type manager', () =>
5555

5656
it('should deserialize type when object is provided and vice versa', () =>
5757
{
58-
const messageManager = new TypeManager();
59-
60-
messageManager.applyTypeOptionsBase(TypeManager.typeOptionsBase);
61-
messageManager.applyTypeOptionsMap(new Map(Array.from(TypeManager.typeOptionsMap)));
62-
58+
const messageManager = TypeManager.clone();
6359
const baseObject = { body: 'a', label: 'b', user: { username: 'a', email: 'b', description: null, about: 'g', device: 'h' }, groups: [{ title: 'a' }, { title: 'a' }] };
6460
const baseObjects = [baseObject, baseObject];
6561
const entity = messageManager.deserialize(Message, baseObject);
@@ -157,11 +153,7 @@ describe('Type manager', () =>
157153

158154
it('should use base type default value when it is enabled', () =>
159155
{
160-
const groupManager = new TypeManager();
161-
162-
groupManager.applyTypeOptionsBase(TypeManager.typeOptionsBase);
163-
groupManager.applyTypeOptionsMap(new Map(Array.from(TypeManager.typeOptionsMap)));
164-
156+
const groupManager = TypeManager.clone();
165157
const baseObject = { title: 'a' };
166158
const entity = groupManager.deserialize(Group, baseObject);
167159

@@ -180,11 +172,7 @@ describe('Type manager', () =>
180172

181173
it('should produce the same result for serialization functions', () =>
182174
{
183-
const groupManager = new TypeManager();
184-
185-
groupManager.applyTypeOptionsBase(TypeManager.typeOptionsBase);
186-
groupManager.applyTypeOptionsMap(new Map(Array.from(TypeManager.typeOptionsMap)));
187-
175+
const groupManager = TypeManager.clone();
188176
const baseObject = { title: 'a' };
189177
const entityA = groupManager.deserialize(Group, baseObject);
190178
const entityB = groupManager.parse(Group, groupManager.stringify(Group, baseObject)) as Group;
@@ -229,10 +217,7 @@ describe('Type manager', () =>
229217

230218
it('should preserve provided configuration', () =>
231219
{
232-
const groupManager = new TypeManager();
233-
234-
groupManager.applyTypeOptionsBase(TypeManager.typeOptionsBase);
235-
groupManager.applyTypeOptionsMap(new Map(Array.from(TypeManager.typeOptionsMap)));
220+
const groupManager = TypeManager.clone();
236221

237222
groupManager.applyTypeOptionsBase({
238223
preserveDiscriminator: true,
@@ -309,6 +294,7 @@ describe('Type manager', () =>
309294
expect(groupMetadata.typeFn.prototype[TypeManager.symbol]).toBeDefined();
310295
expect(groupMetadata.typeFn.prototype[groupManager.symbol]).toBeUndefined();
311296
expect(TypeManager.typeOptionsMap.size).toBeGreaterThan(0);
312-
expect(groupManager.typeOptionsMap.size).toBe(0);
297+
expect(groupManager.typeOptionsMap.size).toBeGreaterThan(0);
298+
expect(groupManager.typeOptionsMap.size).toBe(TypeManager.typeOptionsMap.size);
313299
});
314300
});

src/type-manager.ts

Lines changed: 104 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,13 @@ import { TypeFactory } from './factories/type-factory';
77
import { jsonParse } from './functions/json-parse';
88
import { jsonStringify } from './functions/json-stringify';
99
import { GenericArgument } from './generic-argument';
10+
import { InjectIndex } from './inject-index';
11+
import { InjectOptions } from './inject-options';
1012
import { SingletonInjector } from './injectors/singleton-injector';
1113
import { Logger } from './logger';
1214
import { LoggerLevel } from './logger-level';
15+
import { PropertyName } from './property-name';
16+
import { PropertyOptions } from './property-options';
1317
import { ReferenceCallback } from './reference-callback';
1418
import { CircularReferenceHandler } from './reference-handlers/circular-reference-handler';
1519
import { ReferenceKey } from './reference-key';
@@ -754,14 +758,112 @@ export class TypeManager
754758
*/
755759
public clone(): TypeManager
756760
{
757-
const typeOptionsBase = Object.assign({}, this.typeOptionsBase);
758-
const typeOptionsMap = new Map(Array.from(this.typeOptionsMap));
761+
const typeOptionsBase = this.cloneTypeOptionsBase();
762+
const typeOptionsMap = this.cloneTypeOptionsMap();
759763
const typeManagerOptions = { typeOptionsBase: typeOptionsBase, typeOptionsMap: typeOptionsMap };
760764
const typeManager = new TypeManager(typeManagerOptions);
761765

762766
return typeManager;
763767
}
764768

769+
/**
770+
* Creates a clone of type options base.
771+
*
772+
* @returns {TypeOptionsBase<any>} Type options base clone.
773+
*/
774+
private cloneTypeOptionsBase(): TypeOptionsBase<any>
775+
{
776+
const typeOptionsBase = this.typeOptionsBase;
777+
const customValueMap = typeOptionsBase.customValueMap;
778+
const customValueMapClone = new Map<CustomKey<any>, CustomValue>();
779+
const typeOptionsBaseClone = Object.assign({}, typeOptionsBase, { customValueMap: customValueMapClone });
780+
781+
for (const [customKey, customValue] of customValueMap)
782+
{
783+
customValueMapClone.set(customKey, customValue);
784+
}
785+
786+
return typeOptionsBaseClone;
787+
}
788+
789+
/**
790+
* Creates a clone of type options map.
791+
*
792+
* @returns {Map<TypeFn<any>, TypeOptions<any>>} Type options map clone.
793+
*/
794+
private cloneTypeOptionsMap(): Map<TypeFn<any>, TypeOptions<any>>
795+
{
796+
const typeOptionsMap = this.typeOptionsMap;
797+
const typeOptionsMapClone = new Map<TypeFn<any>, TypeOptions<any>>();
798+
799+
for (const [typeFn, typeOptions] of typeOptionsMap)
800+
{
801+
const typeOptionsClone = Object.assign({}, typeOptions);
802+
const typeCustomValueMap = typeOptions.customValueMap;
803+
804+
if (typeCustomValueMap !== undefined)
805+
{
806+
const typeCustomValueMapClone = new Map<CustomKey<any>, CustomValue>();
807+
808+
for (const [customKey, customValue] of typeCustomValueMap)
809+
{
810+
typeCustomValueMapClone.set(customKey, customValue);
811+
}
812+
813+
typeOptionsClone.customValueMap = typeCustomValueMapClone;
814+
}
815+
816+
const propertyOptionsMap = typeOptions.propertyOptionsMap;
817+
818+
if (propertyOptionsMap !== undefined)
819+
{
820+
const propertyOptionsMapClone = new Map<PropertyName, PropertyOptions<any>>();
821+
822+
for (const [propertyName, propertyOptions] of propertyOptionsMap)
823+
{
824+
const propertyOptionsClone = Object.assign({}, propertyOptions);
825+
const propertyCustomValueMap = propertyOptions.customValueMap;
826+
827+
if (propertyCustomValueMap !== undefined)
828+
{
829+
const propertyCustomValueMapClone = new Map<CustomKey<any>, CustomValue>();
830+
831+
for (const [customKey, customValue] of propertyCustomValueMap)
832+
{
833+
propertyCustomValueMapClone.set(customKey, customValue);
834+
}
835+
836+
propertyOptionsClone.customValueMap = propertyCustomValueMapClone;
837+
}
838+
839+
propertyOptionsMapClone.set(propertyName, propertyOptionsClone);
840+
}
841+
842+
typeOptionsClone.propertyOptionsMap = propertyOptionsMapClone;
843+
}
844+
845+
const injectOptionsMap = typeOptions.injectOptionsMap;
846+
847+
if (injectOptionsMap !== undefined)
848+
{
849+
const injectOptionsMapClone = new Map<InjectIndex, InjectOptions<any>>();
850+
851+
for (const [injectIndex, injectOptions] of injectOptionsMap)
852+
{
853+
const injectOptionsClone = Object.assign({}, injectOptions);
854+
855+
injectOptionsMapClone.set(injectIndex, injectOptionsClone);
856+
}
857+
858+
typeOptionsClone.injectOptionsMap = injectOptionsMapClone;
859+
}
860+
861+
typeOptionsMapClone.set(typeFn, typeOptionsClone);
862+
}
863+
864+
return typeOptionsMapClone;
865+
}
866+
765867
/**
766868
* Clears all type registrations from the static type manager. After calling this
767869
* method, the static type manager will no longer hold any registered type metadata.
@@ -795,21 +897,6 @@ export class TypeManager
795897
typeMetadataSet.clear();
796898
typeFnMap.clear();
797899

798-
const typeManagerOptions = this.typeManagerOptions;
799-
const typeOptionsMap = typeManagerOptions.typeOptionsMap;
800-
801-
if (typeOptionsMap !== undefined)
802-
{
803-
typeOptionsMap.clear();
804-
}
805-
806-
const typeConfigurationMap = typeManagerOptions.typeConfigurationMap;
807-
808-
if (typeConfigurationMap !== undefined)
809-
{
810-
typeConfigurationMap.clear();
811-
}
812-
813900
return this;
814901
}
815902

0 commit comments

Comments
 (0)