From f48722cc3313ecc68fe6d6838578fa8329b1eca0 Mon Sep 17 00:00:00 2001 From: Peter van Vliet Date: Mon, 18 Mar 2024 09:14:36 +0100 Subject: [PATCH] #500: refactored constructor (de)serialization strategy --- .../src/serializers/ClassSerializer.ts | 45 +++++++++++++------ .../src/types/serialized/SerializedClass.ts | 2 +- .../serializers/ClassSerializer.fixture.ts | 25 ++++++----- 3 files changed, 45 insertions(+), 27 deletions(-) diff --git a/packages/serialization/src/serializers/ClassSerializer.ts b/packages/serialization/src/serializers/ClassSerializer.ts index d7ba1fd2..fb612bea 100644 --- a/packages/serialization/src/serializers/ClassSerializer.ts +++ b/packages/serialization/src/serializers/ClassSerializer.ts @@ -36,7 +36,8 @@ export default class ClassSerializer extends ValueSerializer return object instanceof Object && object.serialized === true && typeof object.name === 'string' - && object.args instanceof Array + && object.args instanceof Object + && object.args.constructor === Object && object.fields instanceof Object && object.fields.constructor === Object; } @@ -45,17 +46,17 @@ export default class ClassSerializer extends ValueSerializer { const clazz = reflector.getClass(object); const model = reflector.fromClass(clazz, true); - const parameterNames = this.#extractParameterNames(model); + const parameterNames = this.#extractConstructorParameters(model); const name = clazz.name; const source = (clazz as Loadable).source; - const args: unknown[] = await this.#extractArguments(model, parameterNames, object); - const fields: FlexObject = await this.#extractFields(model, parameterNames, object); + const args: FlexObject = await this.#serializeConstructor(model, parameterNames, object); + const fields: FlexObject = await this.#serializeFields(model, parameterNames, object); return { serialized: true, name: name, source: source, args: args, fields: fields }; } - #extractParameterNames(model: ReflectionClass): string[] + #extractConstructorParameters(model: ReflectionClass): string[] { const constructor = model.getFunction('constructor'); const parameters = (constructor?.parameters ?? []) as ReflectionField[]; @@ -63,9 +64,9 @@ export default class ClassSerializer extends ValueSerializer return parameters.map(parameter => parameter.name); } - async #extractArguments(model: ReflectionClass, includeNames: string[], object: object): Promise + async #serializeConstructor(model: ReflectionClass, includeNames: string[], object: object): Promise { - const args: unknown[] = []; + const args: FlexObject = {}; for (const name of includeNames) { @@ -75,13 +76,13 @@ export default class ClassSerializer extends ValueSerializer ? await this.serializeOther((object as FlexObject)[name]) : undefined; - args.push(objectValue); + args[name] = objectValue; } return args; } - async #extractFields(model: ReflectionClass, excludeNames: string[], object: object): Promise + async #serializeFields(model: ReflectionClass, excludeNames: string[], object: object): Promise { const fields: FlexObject = {}; @@ -115,7 +116,7 @@ export default class ClassSerializer extends ValueSerializer throw new InvalidClass(object.name); } - const args = await Promise.all(object.args.map(async (value) => this.deserializeOther(value))); + const args = await this.#deserializeConstructor(clazz as Function, object.args); const instance = reflector.createInstance(clazz as Function, args) as SerializableObject; @@ -129,13 +130,29 @@ export default class ClassSerializer extends ValueSerializer return instance; } - async #getClass(clazz: Loadable): Promise + async #deserializeConstructor(clazz: Function, args: FlexObject): Promise { - if (clazz.source === undefined) + const model = reflector.fromClass(clazz, true); + const constructor = model.getFunction('constructor'); + const parameters = (constructor?.parameters ?? []) as ReflectionField[]; + + const values = parameters.map(parameter => + { + const value = args[parameter.name]; + + return this.deserializeOther(value); + }); + + return Promise.all(values); + } + + async #getClass(loadable: Loadable): Promise + { + if (loadable.source === undefined) { - return (globalThis as FlexObject)[clazz.name]; + return (globalThis as FlexObject)[loadable.name]; } - return this.#classLoader.loadClass(clazz); + return this.#classLoader.loadClass(loadable); } } diff --git a/packages/serialization/src/types/serialized/SerializedClass.ts b/packages/serialization/src/types/serialized/SerializedClass.ts index 43d90e7e..f0a8bc2e 100644 --- a/packages/serialization/src/types/serialized/SerializedClass.ts +++ b/packages/serialization/src/types/serialized/SerializedClass.ts @@ -4,7 +4,7 @@ import Serialized from '../Serialized.js'; type SerializedClass = Serialized & Loadable & { - args: unknown[], + args: Record, fields: Record } diff --git a/packages/serialization/test/_fixtures/serializers/ClassSerializer.fixture.ts b/packages/serialization/test/_fixtures/serializers/ClassSerializer.fixture.ts index 92d32466..d63d828d 100644 --- a/packages/serialization/test/_fixtures/serializers/ClassSerializer.fixture.ts +++ b/packages/serialization/test/_fixtures/serializers/ClassSerializer.fixture.ts @@ -56,12 +56,13 @@ class PrivateFieldClass { #a: number; #b: boolean; - c?: string; + #c?: string; - constructor(a: number, b: boolean) + constructor(a: number, b: boolean, c?: string) { this.#a = a; this.#b = b; + this.#c = c; } get d() { return this.#a; } @@ -101,20 +102,20 @@ constructedClass.c = 'hello'; const nestedClass = new Nested('hello', constructedClass); const privateClass = new PrivateFieldClass(1, true); -const serializedDataClass = { serialized: true, name: 'Data', source: 'Data', args: [], fields: { a: 1, b: true } }; -const serializedConstructedClass = { serialized: true, name: 'Constructed', source: 'Constructed', args: [1, true], fields: { c: 'hello' } }; -const serializedNestedClass = { serialized: true, name: 'Nested', source: 'Nested', args: ['hello', { serialized: true, name: 'Constructed', source: 'Constructed', args: [1, true], fields: { c: 'hello' } }], fields: {} }; -const serializedPrivateClass = { serialized: true, name: 'PrivateFieldClass', source: 'PrivateFieldClass', args: [undefined, undefined], fields: { c: undefined } }; +const serializedDataClass = { serialized: true, name: 'Data', source: 'Data', args: {}, fields: { a: 1, b: true } }; +const serializedConstructedClass = { serialized: true, name: 'Constructed', source: 'Constructed', args: { a: 1, b: true }, fields: { c: 'hello' } }; +const serializedNestedClass = { serialized: true, name: 'Nested', source: 'Nested', args: { name: 'hello', constructed: { serialized: true, name: 'Constructed', source: 'Constructed', args: { a: 1, b: true }, fields: { c: 'hello' } } }, fields: {} }; +const serializedPrivateClass = { serialized: true, name: 'PrivateFieldClass', source: 'PrivateFieldClass', args: { a: undefined, b: undefined, c: undefined }, fields: { } }; -const serializedInvalidClass = { serialized: true, name: 'Invalid', source: undefined, args: [], fields: {} }; -const serializedUnserializableClass = { serialized: true, name: 'Infinity', source: undefined, args: [], fields: {} }; +const serializedInvalidClass = { serialized: true, name: 'Invalid', source: undefined, args: {}, fields: {} }; +const serializedUnserializableClass = { serialized: true, name: 'Infinity', source: undefined, args: {}, fields: {} }; const nonObject = 42; const nonClassObject = new Object(); -const notSerialized = { name: 'Data', source: undefined, args: [], fields: {} }; -const invalidName = { serialized: true, name: 123, source: undefined, args: [], fields: {} }; -const invalidArgs = { serialized: true, name: 'Data', source: undefined, args: {}, fields: {} }; -const invalidFields = { serialized: true, name: 'Data', source: undefined, args: [], fields: [] }; +const notSerialized = { name: 'Data', source: undefined, args: {}, fields: {} }; +const invalidName = { serialized: true, name: 123, source: undefined, args: {}, fields: {} }; +const invalidArgs = { serialized: true, name: 'Data', source: undefined, args: [], fields: {} }; +const invalidFields = { serialized: true, name: 'Data', source: undefined, args: {}, fields: [] }; export { Data, Constructed, Nested, PrivateFieldClass,