import { Constructor } from "../model/types";
import { Serializer } from "./types";
import { Model } from "../model/Model";
import { ModelKeys } from "./constants";
import { getMetadata } from "../model/utils";
export const DefaultSerializationMethod = "json";
/**
* @summary Concrete implementation of a {@link Serializer} in JSON format
* @description JS's native JSON.stringify (used here) is not deterministic
* and therefore should not be used for hashing purposes
*
* To keep dependencies low, we will not implement this, but we recommend
* implementing a similar {@link JSONSerializer} using 'deterministic-json' libraries
*
* @class JSONSerializer
* @implements Serializer
*
* @category Model
*/
export class JSONSerializer<T extends Model> implements Serializer<T> {
constructor() {}
/**
* @summary prepares the model for serialization
* @description returns a shallow copy of the object, containing an enumerable {@link ModelKeys#ANCHOR} property
* so the object can be recognized upon deserialization
*
* @param {T} model
* @protected
*/
protected preSerialize(model: T) {
// TODO: nested preserialization (so increase performance when deserializing)
const toSerialize: Record<string, any> = Object.assign({}, model);
const metadata = getMetadata(model);
toSerialize[ModelKeys.ANCHOR] = metadata || model.constructor.name;
return toSerialize;
}
/**
* @summary Rebuilds a model from a serialization
* @param {string} str
*
* @throws {Error} If it fails to parse the string, or to build the model
*/
deserialize(str: string): T {
const deserialization = JSON.parse(str);
const className = deserialization[ModelKeys.ANCHOR];
if (!className)
throw new Error("Could not find class reference in serialized model");
const model: T = Model.build(deserialization, className) as unknown as T;
return model;
}
/**
* @summary Serializes a model
* @param {T} model
*
* @throws {Error} if fails to serialize
*/
serialize(model: T): string {
return JSON.stringify(this.preSerialize(model));
}
}
export class Serialization {
private static current: string = DefaultSerializationMethod;
private static cache: Record<string, Serializer<any>> = {
json: new JSONSerializer(),
};
private constructor() {}
private static get(key: string): any {
if (key in this.cache) return this.cache[key];
throw new Error(`No serialization method registered under ${key}`);
}
static register(
key: string,
func: Constructor<Serializer<any>>,
setDefault = false
): void {
if (key in this.cache)
throw new Error(`Serialization method ${key} already registered`);
this.cache[key] = new func();
if (setDefault) this.current = key;
}
static serialize(obj: any, method?: string, ...args: any[]) {
if (!method) return this.get(this.current).serialize(obj, ...args);
return this.get(method).serialize(obj, ...args);
}
static deserialize(obj: string, method?: string, ...args: any[]) {
if (!method) return this.get(this.current).deserialize(obj, ...args);
return this.get(method).deserialize(obj, ...args);
}
static setDefault(method: string) {
this.current = this.get(method);
}
}
Source