From d88ec0155e32286c75e1ce44ff72c1ddb7970bca Mon Sep 17 00:00:00 2001 From: Bas Meeuwissen Date: Mon, 14 Nov 2022 09:24:30 +0100 Subject: [PATCH] Added the date serializer --- documentation/04_basic_features.md | 2 +- .../runtime/serialization/DateSerializer.ts | 39 +++++++++++++++++++ .../runtime/serialization/ValueSerializer.ts | 7 ++++ .../serialization/types/SerializedDate.ts | 9 +++++ .../serialization/ValueSerializer.fixture.ts | 6 +++ .../serialization/ValueSerializer.spec.ts | 25 ++++++++++++ 6 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 jitar/src/runtime/serialization/DateSerializer.ts create mode 100644 jitar/src/runtime/serialization/types/SerializedDate.ts diff --git a/documentation/04_basic_features.md b/documentation/04_basic_features.md index 4b503e9c..b1b68593 100644 --- a/documentation/04_basic_features.md +++ b/documentation/04_basic_features.md @@ -314,7 +314,7 @@ Procedures can make use of a broad variety of data types. When sharing data betw * Primary types (string, number, boolean, null, undefined) * Collection types (array, set, map) -* Object types (plain objects, class objects) +* Object types (plain objects, class objects, dates) Any class object can be transported as long as it can be reconstructed. Private fields are supported, but need access through the constructor or getter / setter. Otherwise, their value will get lost in the transportation process. diff --git a/jitar/src/runtime/serialization/DateSerializer.ts b/jitar/src/runtime/serialization/DateSerializer.ts new file mode 100644 index 00000000..e50bf2d4 --- /dev/null +++ b/jitar/src/runtime/serialization/DateSerializer.ts @@ -0,0 +1,39 @@ + +import Serializer from './interfaces/Serializer.js'; + +import SerializedDate from './types/SerializedDate.js'; +import InvalidPropertyType from './errors/InvalidPropertyType.js'; + +class DateSerializer implements Serializer +{ + serialize(date: Date): SerializedDate + { + return { serialized: true, name: 'Date', value: date.toISOString() }; + } + + async deserialize(serializedDate: SerializedDate): Promise + { + this.#validateDate(serializedDate); + + return new Date(serializedDate.value); + } + + #validateDate(serializedDate: SerializedDate): void + { + if (typeof serializedDate.value !== 'string') + { + throw new InvalidPropertyType('date', 'value', 'string'); + } + + const date = Date.parse(serializedDate.value); + + if (Number.isNaN(date)) + { + throw new InvalidPropertyType('date', 'value', 'date'); + } + } +} + +const instance = new DateSerializer(); + +export default instance; diff --git a/jitar/src/runtime/serialization/ValueSerializer.ts b/jitar/src/runtime/serialization/ValueSerializer.ts index b75ff7cf..ae5b8a79 100644 --- a/jitar/src/runtime/serialization/ValueSerializer.ts +++ b/jitar/src/runtime/serialization/ValueSerializer.ts @@ -7,6 +7,7 @@ import Serializer from './interfaces/Serializer.js'; import MapSerializer from './MapSerializer.js'; import ObjectSerializer from './ObjectSerializer.js'; import SetSerializer from './SetSerializer.js'; +import DateSerializer from './DateSerializer.js'; import SerializableObject from './types/SerializableObject.js'; import Serialized from './types/Serialized.js'; @@ -14,6 +15,7 @@ import SerializedClass from './types/SerializedClass.js'; import SerializedMap from './types/SerializedMap.js'; import SerializedObject from './types/SerializedObject.js'; import SerializedSet from './types/SerializedSet.js'; +import SerializedDate from './types/SerializedDate.js'; class ValueSerializer implements Serializer { @@ -31,6 +33,10 @@ class ValueSerializer implements Serializer { return SetSerializer.serialize(value); } + if (value instanceof Date) + { + return DateSerializer.serialize(value); + } else if (value instanceof Error) { // The error class is like Array, Map and Set in that it's a native class. @@ -71,6 +77,7 @@ class ValueSerializer implements Serializer { case 'Map': return await MapSerializer.deserialize(value as SerializedMap); case 'Set': return await SetSerializer.deserialize(value as SerializedSet); + case 'Date': return await DateSerializer.deserialize(value as SerializedDate); default: return await ClassSerializer.deserialize(value as SerializedClass); } } diff --git a/jitar/src/runtime/serialization/types/SerializedDate.ts b/jitar/src/runtime/serialization/types/SerializedDate.ts new file mode 100644 index 00000000..935e6fc7 --- /dev/null +++ b/jitar/src/runtime/serialization/types/SerializedDate.ts @@ -0,0 +1,9 @@ + +import Serialized from './Serialized.js'; + +type SerializedDate = Serialized & +{ + value: string +}; + +export default SerializedDate; diff --git a/jitar/test/_fixtures/runtime/serialization/ValueSerializer.fixture.ts b/jitar/test/_fixtures/runtime/serialization/ValueSerializer.fixture.ts index 43a57646..67027d09 100644 --- a/jitar/test/_fixtures/runtime/serialization/ValueSerializer.fixture.ts +++ b/jitar/test/_fixtures/runtime/serialization/ValueSerializer.fixture.ts @@ -84,6 +84,11 @@ const emptyObject: unknown = {}; const mixedObject: unknown = { a: 1, b: true, c: 'hello' }; const nestedObject: unknown = { a: 1, b: true, c: { d: false, e: true } }; +const fixedDate = new Date('2021-01-01T00:00:00.000Z'); +const serializedFixedDate = { serialized: true, name: 'Date', value: '2021-01-01T00:00:00.000Z' }; +const serializedInvalidDateValue = { serialized: true, name: 'Date', value: true }; +const serializedInvalidDateString = { serialized: true, name: 'Date', value: 'hello' }; + const emptyMap: Map = new Map(); const mixedMap: Map = new Map().set('a', 1).set('b', true); const nestedMap: Map = new Map().set('b', 'hello').set('c', new Map().set('d', false)); @@ -125,6 +130,7 @@ export numberValue, boolValue, stringValue, nullValue, undefinedValue, emptyArray, mixedArray, nestedArray, emptyObject, mixedObject, nestedObject, + fixedDate, serializedFixedDate, serializedInvalidDateValue, serializedInvalidDateString, emptyMap, mixedMap, nestedMap, serializedEmptyMap, serializedMixedMap, serializedNestedMap, emptySet, mixedSet, nestedSet, serializedEmptySet, serializedMixedSet, serializedNestedSet, dataClass, constructedClass, nestedClass, serializedDataClass, serializedConstructedClass, serializedNestedClass, diff --git a/jitar/test/runtime/serialization/ValueSerializer.spec.ts b/jitar/test/runtime/serialization/ValueSerializer.spec.ts index 0c8138dc..fbc0c774 100644 --- a/jitar/test/runtime/serialization/ValueSerializer.spec.ts +++ b/jitar/test/runtime/serialization/ValueSerializer.spec.ts @@ -4,6 +4,7 @@ import numberValue, boolValue, stringValue, nullValue, undefinedValue, emptyArray, mixedArray, nestedArray, emptyObject, mixedObject, nestedObject, + fixedDate, serializedFixedDate, serializedInvalidDateValue, serializedInvalidDateString, emptyMap, mixedMap, nestedMap, serializedEmptyMap, serializedMixedMap, serializedNestedMap, emptySet, mixedSet, nestedSet, serializedEmptySet, serializedMixedSet, serializedNestedSet, dataClass, constructedClass, nestedClass, serializedDataClass, serializedConstructedClass, serializedNestedClass, @@ -15,6 +16,7 @@ import import ValueSerializer from '../../../src/runtime/serialization/ValueSerializer'; import ClassNotFound from '../../../src/runtime/serialization/errors/ClassNotFound'; import InvalidClass from '../../../src/runtime/serialization/errors/InvalidClass'; +import InvalidPropertyType from '../../../src/runtime/serialization/errors/InvalidPropertyType'; describe('serialization/ValueSerializer', () => { @@ -35,6 +37,13 @@ describe('serialization/ValueSerializer', () => expect(notset).toBe(undefinedValue); }); + it('should serialize date values', () => + { + const date = ValueSerializer.serialize(fixedDate); + + expect(date).toEqual(serializedFixedDate); + }); + it('should serialize arrays', () => { const empty = ValueSerializer.serialize(emptyArray); @@ -124,6 +133,13 @@ describe('serialization/ValueSerializer', () => expect(notset).toBe(undefinedValue); }); + it('should deserialize date values', async () => + { + const date = await ValueSerializer.deserialize(serializedFixedDate); + + expect(date).toEqual(fixedDate); + }); + it('should deserialize arrays', async () => { const empty = await ValueSerializer.deserialize(emptyArray); @@ -199,5 +215,14 @@ describe('serialization/ValueSerializer', () => expect(deserialize).rejects.toEqual(new InvalidClass('Infinity')); }); + + it('should not deserialize invalid date objects', async () => + { + const invalidDateValue = async () => await ValueSerializer.deserialize(serializedInvalidDateValue); + const invalidDateString = async () => await ValueSerializer.deserialize(serializedInvalidDateString); + + expect(invalidDateValue).rejects.toEqual(new InvalidPropertyType('date', 'value', 'string')); + expect(invalidDateString).rejects.toEqual(new InvalidPropertyType('date', 'value', 'date')); + }); }); });