UNPKG

@openhps/core

Version:

Open Hybrid Positioning System - Core component

296 lines (268 loc) 11.5 kB
import { EventEmitter } from 'events'; import { JsonObjectMetadata, Constructor, Serializable, IndexedObject, jsonObject } from 'typedjson'; import { DataSerializerUtils, TypeDescriptor } from './DataSerializerUtils'; import { ObjectMetadata } from './decorators/metadata'; import { Deserializer } from './Deserializer'; import { Serializer } from './Serializer'; import { CustomDeserializerParams, CustomSerializerParams, SerializableMemberOptions } from './decorators/options'; import { SerializableMember } from './decorators/SerializableMember'; export interface MappedTypeConverters<T> { /** * Use this deserializer to convert a JSON value to the type. */ deserializer?: ((json: any, params: CustomDeserializerParams) => T | null | undefined) | null; /** * Use this serializer to convert a type back to JSON. */ serializer?: ((value: T | null | undefined, params: CustomSerializerParams) => any) | null; /** * Custom member serialization strategies. */ members?: Partial<Record<keyof T, SerializableMemberOptions | IndexedObject>>; } JsonObjectMetadata.getFromConstructor = function (ctor) { if (!ctor) { return; } const prototype = ctor.prototype; if (prototype == null) { return; } let metadata: JsonObjectMetadata | undefined; if (Object.prototype.hasOwnProperty.call(prototype, DataSerializerUtils.META_FIELD)) { // The class prototype contains own jsonObject metadata metadata = prototype[DataSerializerUtils.META_FIELD]; } else { const parent = Object.getPrototypeOf(ctor.prototype); if (!parent) { return; } metadata = JsonObjectMetadata.getFromConstructor(parent.constructor); } // Ignore implicitly added jsonObject (through jsonMember) if (metadata?.isExplicitlyMarked === true) { return metadata; } // In the end maybe it is something which we can handle directly if (JsonObjectMetadata['doesHandleWithoutAnnotation'](ctor)) { const primitiveMeta = new JsonObjectMetadata(ctor); primitiveMeta.isExplicitlyMarked = true; // we do not store the metadata here to not modify builtin prototype return primitiveMeta; } }; /** * Allows the serialization and deserialization of objects using the {@link SerializableObject} decorator. * * ## Usage * * ### Registration * Objects are registered upon loading with the {@link SerializableObject} decorator. * Manual registration is possible using: * ```typescript * DataSerializer.registerType(MyObjectClass); * ``` */ export class DataSerializer { protected static readonly knownTypes: Map<string, Serializable<any>> = new Map(); protected static readonly serializer: Serializer = new Serializer(); protected static readonly deserializer: Deserializer = new Deserializer(); /* Event emitter used to listen for registrations and unregister of data types */ protected static eventEmitter: EventEmitter = new EventEmitter(); /** * Manually register a new type * @param {typeof any} type Type to register * @param {MappedTypeConverters} [converters] Optional converters */ static registerType<T>(type: Serializable<T>, converters?: MappedTypeConverters<T>): void { DataSerializer.knownTypes.set(type.name, type); if (converters) { // Set custom serialization strategies if (converters.serializer) { DataSerializer.serializer.setSerializationStrategy(type, (value) => { return converters.serializer(value, { fallback: (so, td) => DataSerializer.serializer.convertSingleValue(so, td as TypeDescriptor), }); }); } if (converters.deserializer) { DataSerializer.deserializer.setDeserializationStrategy(type, (value) => { return converters.deserializer(value, { fallback: (so, td) => DataSerializer.deserializer.convertSingleValue( so, td as TypeDescriptor, DataSerializer.knownTypes, ), }); }); } if (type.name !== 'Object') { // Ensure that the type has a serializable metadata DataSerializerUtils.createMetadata(type.prototype); const options = {}; jsonObject(options)(type); DataSerializer['eventEmitter'].emit('updateSerializableObject', type, options); // Merge const ownMeta = DataSerializerUtils.getMetadata(type); const rootMeta = DataSerializerUtils.getRootMetadata(type.prototype); DataSerializerUtils.updateObjectMetadata(type, options, ownMeta, rootMeta); } // Register members if (converters.members) { Object.keys(converters.members).forEach((key) => { const memberOptions = converters.members[key]; SerializableMember(memberOptions)(type.prototype, key); }); } } DataSerializer.eventEmitter.emit('registerType', type, converters); } static { DataSerializer.registerType(Object, { serializer: (object) => ({ ...Object.keys(object) .map((key) => { return { [key]: typeof object[key] === 'function' ? { function: object[key].toString(), __type: 'Function', } : DataSerializer.serialize(object[key]), }; }) .reduce((a, b) => ({ ...a, ...b }), {}), __type: 'Object', }), deserializer: (objectJson) => Object.keys(objectJson) .map((key) => { if (key === '__type') { return {}; } return { [key]: typeof objectJson[key] === 'object' && objectJson[key].__type === 'Function' ? eval(objectJson[key].function) : DataSerializer.deserialize(objectJson[key]), }; }) .reduce((a, b) => ({ ...a, ...b }), {}), }); } /** * Get the TypedJSON metadata * @deprecated use {@link DataSerializerUtils.getMetadata} * @see {@link https://gist.github.com/krizka/c83fb1966dd57997a1fc02625719387d} * @param {any} proto Prototype of target * @returns {ObjectMetadata} Root object metadata */ static getMetadata(proto: any): ObjectMetadata { return DataSerializerUtils.getMetadata(proto); } /** * Get the root TypedJSON metadata * @deprecated use {@link DataSerializerUtils.getRootMetadata} * @see {@link https://gist.github.com/krizka/c83fb1966dd57997a1fc02625719387d} * @param {any} proto Prototype of target * @returns {ObjectMetadata} Root object metadata */ static getRootMetadata(proto: any): ObjectMetadata { return DataSerializerUtils.getRootMetadata(proto); } /** * Find the root TypedJSON metadata * @deprecated use {@link DataSerializerUtils.getRootMetadata} * @param {any} proto Prototype of target * @returns {ObjectMetadata} Root object metadata */ static findRootMetaInfo(proto: any): ObjectMetadata { return DataSerializerUtils.getRootMetadata(proto); } /** * Unregister a type * @param {typeof any} type Type to unregister */ static unregisterType(type: Serializable<any>): void { DataSerializer.knownTypes.delete(type.name); DataSerializer.eventEmitter.emit('unregisterType', type); } static findTypeByName(name: string): Serializable<any> { return DataSerializer.knownTypes.get(name); } /** * Clone a serializable object * @param {any} object Serializable object * @param {Constructor<any>} [dataType] Data type to clone to * @returns {any} Cloned object */ static clone<T, D = T>(object: T, dataType?: Constructor<D>): D { return DataSerializer.deserialize(DataSerializer.serialize(object), dataType); } /** * Serialize data * @param {any} data Data to serialize * @param {DataSerializerConfig} [config] Data serializer configuration * @returns {any} Serialized data */ static serialize<T>(data: T, config: DataSerializerConfig = {}): any { if (data === null || data === undefined) { return undefined; } const globalDataType = Object.getPrototypeOf(data).constructor; // First check if it is a registered type // this is important as some serializable classes // may extend an array if (!DataSerializer.findTypeByName(globalDataType.name) && Array.isArray(data)) { return data.map(DataSerializer.serialize.bind(DataSerializer)); } const serializer = config.serializer ?? DataSerializer.serializer; return serializer.convertSingleValue( data, DataSerializerUtils.ensureTypeDescriptor(globalDataType), undefined, undefined, config, ); } /** * Deserialize data * @param serializedData Data to deserialze * @param dataType Optional data type to specify deserialization type * @param config Data serializer configuration */ static deserialize<T>(serializedData: any, dataType?: Constructor<T>, config?: DataSerializerConfig): T; static deserialize<T>(serializedData: any[], dataType?: Constructor<T>, config?: DataSerializerConfig): T[]; static deserialize<T>(serializedData: any, dataType?: Constructor<T>, config: DataSerializerConfig = {}): T | T[] { if ((typeof serializedData !== 'object' && typeof serializedData !== 'function') || !serializedData) { return serializedData; } if (Array.isArray(serializedData)) { return serializedData.map((serializedObject) => DataSerializer.deserialize(serializedObject)); } const deserializer = config.deserializer ?? DataSerializer.deserializer; const finalType = dataType ?? deserializer.getTypeResolver()(serializedData, DataSerializer.knownTypes); return deserializer.convertSingleValue( serializedData, DataSerializerUtils.ensureTypeDescriptor(finalType), DataSerializer.knownTypes, undefined, undefined, config, ); } } export interface DataSerializerConfig { /** * Set the serializer used for serializing. * @default TypedJSON JSON serializer */ serializer?: Serializer; /** * Set the deserializer used for deserializing. * @default TypedJSON JSON deserializer */ deserializer?: Deserializer; }