src/data/Deserializer.ts
import { Deserializer as JSONDeserializer } from 'typedjson/lib/cjs/deserializer';
import type { TypeDescriptor } from 'typedjson/lib/types/type-descriptor';
import {
ensureTypeDescriptor,
ConcreteTypeDescriptor,
ArrayTypeDescriptor,
MapTypeDescriptor,
SetTypeDescriptor,
} from 'typedjson/lib/cjs/type-descriptor';
import { IndexedObject, JsonObjectMetadata, Serializable, TypeResolver } from 'typedjson';
import { ObjectMemberMetadata } from './decorators/metadata';
import type { OptionsBase } from 'typedjson/lib/types/options-base';
import { mergeOptions } from 'typedjson/lib/cjs/options-base';
import { isSubtypeOf, isValueDefined, nameof } from 'typedjson/lib/cjs/helpers';
import { BufferUtils } from '../utils/BufferUtils';
export class Deserializer extends JSONDeserializer {
protected declare options?: OptionsBase;
protected declare deserializationStrategy: Map<Serializable<any>, DeserializerFn<any, TypeDescriptor, any>>;
protected errorHandler: (error: Error) => void = (e: Error) => {
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;
};
protected typeResolver(sourceObject: IndexedObject, knownTypes: Map<string, Serializable<any>>) {
return sourceObject['__type'] !== undefined
? knownTypes.get(sourceObject.__type)
: sourceObject.constructor ?? Object;
}
protected declare nameResolver?: (ctor: Serializable<any>) => string;
declare setDeserializationStrategy: (
type: Serializable<any>,
deserializer: DeserializerFn<any, TypeDescriptor, any>,
) => void;
declare setNameResolver: (nameResolverCallback: (ctor: Serializable<any>) => string) => void;
declare setTypeResolver: (typeResolverCallback: TypeResolver) => void;
declare getTypeResolver: () => TypeResolver;
declare setErrorHandler: (errorHandlerCallback: (error: Error) => void) => void;
declare getErrorHandler: () => (error: Error) => void;
declare instantiateType: (ctor: any) => any;
declare mergeKnownTypes: (
...knownTypeMaps: Array<Map<string, Serializable<any>>>
) => Map<string, Serializable<any>>;
declare createKnownTypesMap: (knowTypes: Set<Serializable<any>>) => Map<string, Serializable<any>>;
declare retrievePreserveNull: (memberOptions?: ObjectMemberMetadata) => boolean;
constructor() {
super();
this.setDeserializationStrategy(Map, this.convertAsMap.bind(this));
this.setDeserializationStrategy(Array, this.convertAsArray.bind(this));
this.setDeserializationStrategy(Set, this.convertAsSet.bind(this));
this.setDeserializationStrategy(Uint8Array, BufferUtils.fromHexString);
}
convertSingleValue(
sourceObject: any,
typeDescriptor: TypeDescriptor,
knownTypes: Map<string, Serializable<any>>,
memberName?: string,
memberOptions?: ObjectMemberMetadata,
serializerOptions?: any,
): any {
return this._convertSingleValue.bind(this)(
sourceObject,
typeDescriptor,
knownTypes,
memberName,
memberOptions,
serializerOptions,
);
}
private _convertSingleValue(
sourceObject: any,
typeDescriptor: TypeDescriptor,
knownTypes: Map<string, Serializable<any>>,
memberName: string,
memberOptions?: ObjectMemberMetadata,
serializerOptions?: any,
): any {
if (this.retrievePreserveNull(memberOptions) && sourceObject === null) {
return null;
} else if (!isValueDefined(sourceObject)) {
return;
}
const deserializer = this.deserializationStrategy.get(typeDescriptor.ctor);
if (deserializer !== undefined) {
return deserializer(
sourceObject,
typeDescriptor,
knownTypes,
memberName,
this,
memberOptions,
serializerOptions,
);
}
if (typeof sourceObject === 'object') {
return this.convertAsObject(
sourceObject,
typeDescriptor,
knownTypes,
memberName,
this,
memberOptions,
serializerOptions,
);
}
let error = `Could not deserialize '${memberName}'; don't know how to deserialize type`;
if (typeDescriptor.hasFriendlyName()) {
error += ` '${typeDescriptor.ctor.name}'`;
}
this.errorHandler(new TypeError(`${error}.`));
}
convertAsObject<T>(
sourceObject: IndexedObject,
typeDescriptor: ConcreteTypeDescriptor,
knownTypes: Map<string, Serializable<any>>,
memberName: string,
deserializer: Deserializer,
memberOptions?: ObjectMemberMetadata,
serializerOptions?: any,
): IndexedObject | T | undefined {
if ((typeof sourceObject as any) !== 'object' || (sourceObject as any) === null) {
deserializer.getErrorHandler()(
new TypeError(`Cannot deserialize ${memberName}: 'sourceObject' must be a defined object.`),
);
return undefined;
}
let expectedSelfType = typeDescriptor.ctor;
let sourceObjectMetadata = JsonObjectMetadata.getFromConstructor(expectedSelfType);
let typeResolver = deserializer.getTypeResolver();
if (sourceObjectMetadata !== undefined) {
if (sourceObjectMetadata.typeResolver != null) {
typeResolver = sourceObjectMetadata.typeResolver;
}
}
// Check if a type-hint is available from the source object.
const typeFromTypeHint = typeResolver(sourceObject, knownTypes);
if (typeFromTypeHint != null) {
// Check if type hint is a valid subtype of the expected source type.
if (isSubtypeOf(typeFromTypeHint, expectedSelfType)) {
// Hell yes.
expectedSelfType = typeFromTypeHint;
sourceObjectMetadata = JsonObjectMetadata.getFromConstructor(typeFromTypeHint);
}
}
if (sourceObjectMetadata?.isExplicitlyMarked === true) {
const sourceMetadata = sourceObjectMetadata;
// Strong-typed deserialization available, get to it.
// First deserialize properties into a temporary object.
const sourceObjectWithDeserializedProperties = {} as IndexedObject;
const classOptions = mergeOptions(deserializer.options, sourceMetadata.options);
// Deserialize by expected properties.
sourceMetadata.dataMembers.forEach((objMemberMetadata, propKey) => {
const objMemberValue = sourceObject[propKey];
const objMemberDebugName = `${nameof(sourceMetadata.classType)}.${propKey}`;
const objMemberOptions = mergeOptions(classOptions, objMemberMetadata.options);
let revivedValue;
if (objMemberMetadata.deserializer != null) {
revivedValue = objMemberMetadata.deserializer(objMemberValue, {
fallback: (so, td) => deserializer.convertSingleValue(so, ensureTypeDescriptor(td), knownTypes),
});
} else if (objMemberMetadata.type == null) {
throw new TypeError(
`Cannot deserialize ${objMemberDebugName} there is` +
` no constructor nor deserialization function to use.`,
);
} else {
revivedValue = deserializer.convertSingleValue(
objMemberValue,
objMemberMetadata.type(),
knownTypes,
objMemberDebugName,
objMemberOptions,
serializerOptions,
);
}
// @todo revivedValue will never be null in RHS of ||
if (
isValueDefined(revivedValue) ||
(deserializer.retrievePreserveNull(objMemberOptions) && (revivedValue as any) === null)
) {
sourceObjectWithDeserializedProperties[objMemberMetadata.key] = revivedValue;
} else if (objMemberMetadata.isRequired === true) {
deserializer.getErrorHandler()(new TypeError(`Missing required member '${objMemberDebugName}'.`));
}
});
// Next, instantiate target object.
let targetObject: IndexedObject;
if (typeof sourceObjectMetadata.initializerCallback === 'function') {
try {
targetObject = sourceObjectMetadata.initializerCallback(
sourceObjectWithDeserializedProperties,
sourceObject,
);
// Check the validity of user-defined initializer callback.
if ((targetObject as any) == null) {
throw new TypeError(
`Cannot deserialize ${memberName}:` +
` 'initializer' function returned undefined/null` +
`, but '${nameof(sourceObjectMetadata.classType)}' was expected.`,
);
} else if (!(targetObject instanceof sourceObjectMetadata.classType)) {
throw new TypeError(
`Cannot deserialize ${memberName}:` +
`'initializer' returned '${nameof(targetObject.constructor)}'` +
`, but '${nameof(sourceObjectMetadata.classType)}' was expected` +
`, and '${nameof(targetObject.constructor)}' is not a subtype of` +
` '${nameof(sourceObjectMetadata.classType)}'`,
);
}
} catch (e) {
deserializer.getErrorHandler()(e);
return undefined;
}
} else {
targetObject = deserializer.instantiateType(expectedSelfType);
}
// Finally, assign deserialized properties to target object.
Object.assign(targetObject, sourceObjectWithDeserializedProperties);
// Call onDeserialized method (if any).
const methodName = sourceObjectMetadata.onDeserializedMethodName;
if (methodName != null) {
if (typeof (targetObject as any)[methodName] === 'function') {
// check for member first
(targetObject as any)[methodName]();
} else if (typeof (targetObject.constructor as any)[methodName] === 'function') {
// check for static
(targetObject.constructor as any)[methodName]();
} else {
deserializer.getErrorHandler()(
new TypeError(
`onDeserialized callback` +
`'${nameof(sourceObjectMetadata.classType)}.${methodName}' is not a method.`,
),
);
}
}
return targetObject;
} else {
// Untyped deserialization into Object instance.
const targetObject = {} as IndexedObject;
Object.keys(sourceObject).forEach((sourceKey) => {
targetObject[sourceKey] = deserializer.convertSingleValue(
sourceObject[sourceKey],
new ConcreteTypeDescriptor(sourceObject[sourceKey].constructor),
knownTypes,
sourceKey,
memberOptions,
serializerOptions,
);
});
return targetObject;
}
}
convertAsArray(
sourceObject: any,
typeDescriptor: ArrayTypeDescriptor,
knownTypes: Map<string, Serializable<any>>,
memberName: string,
deserializer: Deserializer,
memberOptions?: ObjectMemberMetadata,
serializableOptions?: any,
): Array<any> {
if (!(typeDescriptor instanceof ArrayTypeDescriptor)) {
throw new TypeError(
`Could not deserialize ${memberName} as Array: incorrect TypeDescriptor detected,` +
' please use proper annotation or function for this type',
);
}
if (!Array.isArray(sourceObject)) {
deserializer.getErrorHandler()(
new TypeError(this.makeTypeErrorMessage(Array, sourceObject.constructor, memberName)),
);
return [];
}
if ((typeDescriptor.elementType as any) == null) {
deserializer.getErrorHandler()(
new TypeError(
`Could not deserialize ${memberName} as Array: missing constructor reference of` +
` Array elements.`,
),
);
return [];
}
return sourceObject.map((element, i) => {
// If an array element fails to deserialize, substitute with undefined. This is so that the
// original ordering is not interrupted by faulty
// entries, as an Array is ordered.
try {
return deserializer.convertSingleValue(
element,
typeDescriptor.elementType,
knownTypes,
`${memberName}[${i}]`,
memberOptions,
serializableOptions,
);
} catch (e) {
deserializer.getErrorHandler()(e);
// Keep filling the array here with undefined to keep original ordering.
// Note: this is just aesthetics, not returning anything produces the same result.
return undefined;
}
});
}
convertAsSet(
sourceObject: any,
typeDescriptor: SetTypeDescriptor,
knownTypes: Map<string, Serializable<any>>,
memberName: string,
deserializer: Deserializer,
memberOptions?: ObjectMemberMetadata,
serializableOptions?: any,
): Set<any> {
if (!(typeDescriptor instanceof SetTypeDescriptor)) {
throw new TypeError(
`Could not deserialize ${memberName} as Set: incorrect TypeDescriptor detected,` +
` please use proper annotation or function for this type`,
);
}
if (!Array.isArray(sourceObject)) {
deserializer.getErrorHandler()(
new TypeError(this.makeTypeErrorMessage(Array, sourceObject.constructor, memberName)),
);
return new Set<any>();
}
if ((typeDescriptor.elementType as any) == null) {
deserializer.getErrorHandler()(
new TypeError(
`Could not deserialize ${memberName} as Set: missing constructor reference of` + ` Set elements.`,
),
);
return new Set<any>();
}
const resultSet = new Set<any>();
sourceObject.forEach((element, i) => {
try {
resultSet.add(
deserializer.convertSingleValue(
element,
typeDescriptor.elementType,
knownTypes,
`${memberName}[${i}]`,
memberOptions,
serializableOptions,
),
);
} catch (e) {
// Faulty entries are skipped, because a Set is not ordered, and skipping an entry
// does not affect others.
deserializer.getErrorHandler()(e);
}
});
return resultSet;
}
convertAsMap(
sourceObject: any,
typeDescriptor: MapTypeDescriptor,
knownTypes: Map<string, Serializable<any>>,
memberName: string,
deserializer: Deserializer,
memberOptions?: ObjectMemberMetadata,
serializableOptions?: any,
): Map<any, any> {
if (!(typeDescriptor instanceof MapTypeDescriptor)) {
throw new TypeError(
`Could not deserialize ${memberName} as Map: incorrect TypeDescriptor detected,` +
'please use proper annotation or function for this type',
);
}
const expectedShape = typeDescriptor.getCompleteOptions().shape;
if (!this.isExpectedMapShape(sourceObject, expectedShape)) {
const expectedType = expectedShape === 0 ? Array : Object;
deserializer.getErrorHandler()(
new TypeError(this.makeTypeErrorMessage(expectedType, sourceObject.constructor, memberName)),
);
return new Map<any, any>();
}
if ((typeDescriptor.keyType as any) == null) {
deserializer.getErrorHandler()(
new TypeError(`Could not deserialize ${memberName} as Map: missing key constructor.`),
);
return new Map<any, any>();
}
if ((typeDescriptor.valueType as any) == null) {
deserializer.getErrorHandler()(
new TypeError(`Could not deserialize ${memberName} as Map: missing value constructor.`),
);
return new Map<any, any>();
}
const keyMemberName = `${memberName}[].key`;
const valueMemberName = `${memberName}[].value`;
const resultMap = new Map<any, any>();
if (expectedShape.name === 'OBJECT') {
Object.keys(sourceObject).forEach((key) => {
try {
const resultKey = deserializer.convertSingleValue(
key,
typeDescriptor.keyType,
knownTypes,
keyMemberName,
memberOptions,
serializableOptions,
);
if (isValueDefined(resultKey)) {
resultMap.set(
resultKey,
deserializer.convertSingleValue(
sourceObject[key],
typeDescriptor.valueType,
knownTypes,
valueMemberName,
memberOptions,
serializableOptions,
),
);
}
} catch (e) {
// Faulty entries are skipped, because a Map is not ordered,
// and skipping an entry does not affect others.
deserializer.getErrorHandler()(e);
}
});
} else {
sourceObject.forEach((element: any) => {
try {
const key = deserializer.convertSingleValue(
element.key,
typeDescriptor.keyType,
knownTypes,
keyMemberName,
memberOptions,
serializableOptions,
);
// Undefined/null keys not supported, skip if so.
if (isValueDefined(key)) {
resultMap.set(
key,
deserializer.convertSingleValue(
element.value,
typeDescriptor.valueType,
knownTypes,
valueMemberName,
memberOptions,
serializableOptions,
),
);
}
} catch (e) {
// Faulty entries are skipped, because a Map is not ordered,
// and skipping an entry does not affect others.
deserializer.getErrorHandler()(e);
}
});
}
return resultMap;
}
protected isExpectedMapShape(source: any, expectedShape: any): boolean {
return (expectedShape === 0 && Array.isArray(source)) || (expectedShape === 1 && typeof source === 'object');
}
protected makeTypeErrorMessage(
expectedType: Serializable<any> | string,
actualType: Serializable<any> | string,
memberName: string,
) {
const expectedTypeName = typeof expectedType === 'function' ? nameof(expectedType) : expectedType;
const actualTypeName = typeof actualType === 'function' ? nameof(actualType) : actualType;
return `Could not deserialize ${memberName}: expected '${expectedTypeName}',` + ` got '${actualTypeName}'.`;
}
}
export type DeserializerFn<T, TD extends TypeDescriptor, Raw> = (
sourceObject: Raw,
typeDescriptor: TD,
knownTypes: Map<string, Serializable<any>>,
memberName: string,
deserializer: Deserializer,
memberOptions?: ObjectMemberMetadata,
serializerOptions?: any,
) => T;