UNPKG

@openhps/core

Version:

Open Hybrid Positioning System - Core component

242 lines 13.2 kB
import { Serializer as JSONSerializer } from "typedjson/lib/esm/serializer"; import { ensureTypeDescriptor, ArrayTypeDescriptor, MapTypeDescriptor, SetTypeDescriptor } from "typedjson/lib/esm/type-descriptor"; import { AnyT, JsonObjectMetadata } from 'typedjson'; import { isInstanceOf, isValueDefined, nameof } from "typedjson/lib/esm/helpers"; import { mergeOptions } from "typedjson/lib/esm/options-base"; import { BufferUtils } from '../utils/BufferUtils'; export class Serializer extends JSONSerializer { constructor() { super(); this.errorHandler = e => { e.message = e.message.replace('@jsonObject', '@SerializableObject()'); e.message = e.message.replace('@jsonMember', '@SerializableMember()'); e.message = e.message.replace('@jsonSetMember', '@SerializableSetMember()'); e.message = e.message.replace('@jsonMapMember', '@SerializableMapMember()'); e.message = e.message.replace('@jsonArrayMember', '@SerializableArrayMember()'); throw e; }; this.setSerializationStrategy(Map, this.convertAsMap.bind(this)); this.setSerializationStrategy(Array, this.convertAsArray.bind(this)); this.setSerializationStrategy(Set, this.convertAsSet.bind(this)); this.setSerializationStrategy(Uint8Array, BufferUtils.toHexString); } convertSingleValue(sourceObject, typeDescriptor, memberName, memberOptions, serializerOptions) { const targetObject = this._convertSingleValue.bind(this)(sourceObject, typeDescriptor, memberName, memberOptions, serializerOptions); if (memberName === undefined && typeof targetObject === 'object') { targetObject.__type = typeDescriptor.ctor.name; } return targetObject; } _convertSingleValue(sourceObject, typeDescriptor, memberName, memberOptions, serializerOptions) { if (this.retrievePreserveNull(memberOptions) && sourceObject === null) { return null; } if (!isValueDefined(sourceObject)) { return; } if (!isInstanceOf(sourceObject, typeDescriptor.ctor)) { const expectedName = nameof(typeDescriptor.ctor); const actualName = nameof(sourceObject.constructor); this.errorHandler(new TypeError(`Could not serialize '${memberName}': expected '${expectedName}',` + ` got '${actualName}'.`)); return; } const serializer = this.serializationStrategy.get(typeDescriptor.ctor); if (serializer !== undefined) { return serializer(sourceObject, typeDescriptor, memberName, this, memberOptions, serializerOptions); } // if not present in the strategy do property by property serialization if (typeof sourceObject === 'object') { return this.convertAsObject(sourceObject, typeDescriptor, memberName, this, memberOptions, serializerOptions); } let error = `Could not serialize '${memberName}'; don't know how to serialize type`; if (typeDescriptor.hasFriendlyName()) { error += ` '${typeDescriptor.ctor.name}'`; } this.errorHandler(new TypeError(`${error}.`)); } convertAsObject(sourceObject, typeDescriptor, memberName, serializer, memberOptions, serializerOptions) { let sourceTypeMetadata; let targetObject; let typeHintEmitter = serializer.getTypeHintEmitter(); if (sourceObject.constructor !== typeDescriptor.ctor && sourceObject instanceof typeDescriptor.ctor) { // The source object is not of the expected type, but it is a valid subtype. // This is OK, and we'll proceed to gather object metadata from the subtype instead. sourceTypeMetadata = JsonObjectMetadata.getFromConstructor(sourceObject.constructor); } else { sourceTypeMetadata = JsonObjectMetadata.getFromConstructor(typeDescriptor.ctor); } if (sourceTypeMetadata === undefined) { // Untyped serialization, "as-is", we'll just pass the object on. // We'll clone the source object, because type hints are added to the object itself, and we // don't want to modify // to the original object. targetObject = Object.assign({}, sourceObject); } else { const beforeSerializationMethodName = sourceTypeMetadata.beforeSerializationMethodName; if (beforeSerializationMethodName != null) { if (typeof sourceObject[beforeSerializationMethodName] === 'function') { // check for member first sourceObject[beforeSerializationMethodName](); } else if (typeof sourceObject.constructor[beforeSerializationMethodName] === 'function') { // check for static sourceObject.constructor[beforeSerializationMethodName](); } else { serializer.getErrorHandler()(new TypeError(`beforeSerialization callback '` + `${nameof(sourceTypeMetadata.classType)}.${beforeSerializationMethodName}` + `' is not a method.`)); } } const sourceMeta = sourceTypeMetadata; // Strong-typed serialization available. // We'll serialize by members that have been marked with @jsonMember (including // array/set/map members), and perform recursive conversion on each of them. The converted // objects are put on the 'targetObject', which is what will be put into 'JSON.stringify' // finally. targetObject = {}; const classOptions = mergeOptions(serializer.options, sourceMeta.options); if (sourceMeta.typeHintEmitter != null) { typeHintEmitter = sourceMeta.typeHintEmitter; } sourceMeta.dataMembers.forEach(objMemberMetadata => { const objMemberOptions = mergeOptions(classOptions, objMemberMetadata.options); let serialized; const value = sourceObject[objMemberMetadata.key]; if (objMemberMetadata.serializer != null) { serialized = objMemberMetadata.serializer(value, { fallback: (so, td) => serializer.convertSingleValue(so, ensureTypeDescriptor(td)) }); } else if (objMemberMetadata.type == null) { throw new TypeError(`Could not serialize ${objMemberMetadata.name}, there is` + ` no constructor nor serialization function to use.`); } else if (objMemberMetadata.type() === AnyT && value !== null && value !== undefined) { // Any type, serialize as an unknown object const globalDataType = Object.getPrototypeOf(value).constructor; serialized = serializer.convertSingleValue(value, globalDataType ? ensureTypeDescriptor(globalDataType) : objMemberMetadata.type(), `${nameof(sourceMeta.classType)}.${objMemberMetadata.key}`, objMemberOptions, serializerOptions); if (typeof serialized === 'object' && serialized !== null) { serialized.__type = globalDataType ? globalDataType.name : 'Object'; } } else { serialized = serializer.convertSingleValue(value, objMemberMetadata.type(), `${nameof(sourceMeta.classType)}.${objMemberMetadata.key}`, objMemberOptions, serializerOptions); } if (serializer.retrievePreserveNull(objMemberOptions) && serialized === null || isValueDefined(serialized)) { targetObject[objMemberMetadata.name] = serialized; } }); } // Add type-hint. typeHintEmitter(targetObject, sourceObject, typeDescriptor.ctor, sourceTypeMetadata); return targetObject; } /** * Performs the conversion of an array of typed objects (or primitive values) to an array of simple * javascript objects * (or primitive values) for serialization. * @param sourceObject Source object to convert * @param typeDescriptor Type descriptor of source object * @param memberName Member name to convert * @param serializer Serializer * @param memberOptions Member options of memberName * @param serializerOptions Custom serializer options */ convertAsArray(sourceObject, typeDescriptor, memberName, serializer, memberOptions, serializerOptions) { if (!(typeDescriptor instanceof ArrayTypeDescriptor)) { throw new TypeError(`Could not serialize ${memberName} as Array: incorrect TypeDescriptor detected, please` + ' use proper annotation or function for this type'); } if (typeDescriptor.elementType == null) { throw new TypeError(`Could not serialize ${memberName} as Array: missing element type definition.`); } // Check the type of each element, individually. // If at least one array element type is incorrect, we return undefined, which results in no // value emitted during serialization. This is so that invalid element types don't unexpectedly // alter the ordering of other, valid elements, and that no unexpected undefined values are in // the emitted array. sourceObject.forEach((element, i) => { if (!(serializer.retrievePreserveNull(memberOptions) && element === null) && !isInstanceOf(element, typeDescriptor.elementType.ctor)) { const expectedTypeName = nameof(typeDescriptor.elementType.ctor); const actualTypeName = element && nameof(element.constructor); throw new TypeError(`Could not serialize ${memberName}[${i}]:` + ` expected '${expectedTypeName}', got '${actualTypeName}'.`); } }); return sourceObject.map((element, i) => { return serializer.convertSingleValue(element, typeDescriptor.elementType, `${memberName}[${i}]`, memberOptions, serializerOptions); }); } /** * Performs the conversion of a set of typed objects (or primitive values) into an array * of simple javascript objects. * @param sourceObject * @param typeDescriptor * @param memberName * @param serializer * @param memberOptions * @param serializerOptions * @returns */ convertAsSet(sourceObject, typeDescriptor, memberName, serializer, memberOptions, serializerOptions) { if (!(typeDescriptor instanceof SetTypeDescriptor)) { throw new TypeError(`Could not serialize ${memberName} as Set: incorrect TypeDescriptor detected, please` + ' use proper annotation or function for this type'); } if (typeDescriptor.elementType == null) { throw new TypeError(`Could not serialize ${memberName} as Set: missing element type definition.`); } memberName += '[]'; const resultArray = []; // Convert each element of the set, and put it into an output array. // The output array is the one serialized, as JSON.stringify does not support Set serialization. // (TODO: clarification needed) sourceObject.forEach(element => { const resultElement = serializer.convertSingleValue(element, typeDescriptor.elementType, memberName, memberOptions, serializerOptions); // Add to output if the source element was undefined, OR the converted element is defined. // This will add intentionally undefined values to output, but not values that became // undefined DURING serializing (usually because of a type-error). if (!isValueDefined(element) || isValueDefined(resultElement)) { resultArray.push(resultElement); } }); return resultArray; } /** * Performs the conversion of a map of typed objects (or primitive values) into an array * of simple javascript objects with `key` and `value` properties. * @param sourceObject * @param typeDescriptor * @param memberName * @param serializer * @param memberOptions * @param serializerOptions */ convertAsMap(sourceObject, typeDescriptor, memberName, serializer, memberOptions, serializerOptions) { if (!(typeDescriptor instanceof MapTypeDescriptor)) { // Serialize as object return serializer.convertAsObject(sourceObject, typeDescriptor, memberName, serializer, memberOptions); } if (typeDescriptor.valueType == null) { // @todo Check type throw new TypeError(`Could not serialize ${memberName} as Map: missing value type definition.`); } if (typeDescriptor.keyType == null) { // @todo Check type throw new TypeError(`Could not serialize ${memberName} as Map: missing key type definition.`); } const keyMemberName = `${memberName}[].key`; const valueMemberName = `${memberName}[].value`; const resultShape = typeDescriptor.getCompleteOptions().shape; const result = resultShape === 1 ? {} : []; const preserveNull = serializer.retrievePreserveNull(memberOptions); // Convert each *entry* in the map to a simple javascript object with key and value properties. sourceObject.forEach((value, key) => { const resultKeyValuePairObj = { key: serializer.convertSingleValue(key, typeDescriptor.keyType, keyMemberName, memberOptions, serializerOptions), value: serializer.convertSingleValue(value, typeDescriptor.valueType, valueMemberName, memberOptions, serializerOptions) }; // We are not going to emit entries with undefined keys OR undefined values. const keyDefined = isValueDefined(resultKeyValuePairObj.key); const valueDefined = resultKeyValuePairObj.value === null && preserveNull || isValueDefined(resultKeyValuePairObj.value); if (keyDefined && valueDefined) { if (resultShape === 1) { result[resultKeyValuePairObj.key] = resultKeyValuePairObj.value; } else { result.push(resultKeyValuePairObj); } } }); return result; } }