UNPKG

serialijse

Version:

serialize and deserialize your javascript objects, preserve your object model

526 lines (446 loc) 17.9 kB
(function (exports) { "use strict"; var assert = require("assert"); var b = require("buffer"); var g_classInfos = {}; // note: phantomjs may not define Object.assign by default so we need the pony fill var objectAssign = Object.assign || require("object-assign"); var isFunction = function (obj) { return typeof obj === 'function' || obj.prototype; }; function merge_options(options1, options2) { return objectAssign({}, options1, options2); } function serializeObject(context, object, rawData, global_options) { assert(!rawData.hasOwnProperty("d")); rawData.d = {}; var options = global_options || {}; if (object.constructor && object.constructor.serialijseOptions) { options = merge_options(options, object.constructor.serialijseOptions); } if (options.ignored) { options.ignored = (options.ignored instanceof Array) ? options.ignored : [options.ignored]; } for (var property in object) { if (isPropertyPersistable(object, property, options)) { if (object[property] !== null) { rawData.d[property] = _serialize(context, options, object[property]); } else { rawData.d[property] = null; } } } } function deserializeObject(context, object_id, object_definition) { var classInfo = this; var object = new classInfo.constructor(); context.cache[object_id] = object; var rawData = object_definition.d; // istanbul ignore next if (!rawData) { return; // no properties } for (var property in rawData) { if (rawData.hasOwnProperty(property)) { try { object[property] = deserialize_node_or_value(context, rawData[property]); } catch (err) // istanbul ignore next { console.log(" property : ", property); console.log(err); throw err; } } } return object; } function serializeTypedArray(context, typedArray, rawData) { rawData.a = Buffer.from(typedArray.buffer).toString("base64"); } function deserializeTypedArray(context, object_id, rawData) { var classInfo = this; assert(typeof rawData.a === "string"); var buf = Buffer.from(rawData.a, "base64"); var tmp = new Uint8Array(buf); var obj = new classInfo.constructor(tmp.buffer); context.cache[object_id] = obj; return obj; } function deserialize_node_or_value(context, node) { assert(context); if ("object" === typeof node) { return deserialize_node(context, node); } return node; } function declarePersistable(constructor, name, serializeFunc, deserializeFunc) { var className = constructor.prototype.constructor.name || constructor.name; serializeFunc = serializeFunc || serializeObject; deserializeFunc = deserializeFunc || deserializeObject; if (name) { className = name; } // istanbul ignore next if (g_classInfos.hasOwnProperty(className)) { console.warn("declarePersistable warning: declarePersistable : class " + className + " already registered"); } // istanbul ignore next if (!(constructor instanceof Function) && !constructor.prototype) { throw new Error("declarePersistable: Cannot find constructor for " + className); }; g_classInfos[className] = { constructor: constructor, serializeFunc: serializeFunc, deserializeFunc: deserializeFunc }; } function declareTypedArrayPersistable(typeArrayName) { // istanbul ignore next if (!global[typeArrayName]) { console.log("warning : " + typeArrayName + " is not supported in this environment"); return; } var constructor = global[typeArrayName]; // repair constructor name if any // istanbul ignore next if (!constructor.name) { constructor.name = typeArrayName; } // if (!(constructor instanceof Function)) { // a = new constructor(); // throw new Error("warning : " + typeArrayName + " is not supported FULLY FILLY in this environment" +typeof constructor,typeof constructor.constructor); // } declarePersistable(constructor, typeArrayName, serializeTypedArray, deserializeTypedArray); } declarePersistable(Object, "Object", serializeObject, deserializeObject); declareTypedArrayPersistable("Float32Array"); declareTypedArrayPersistable("Float64Array"); declareTypedArrayPersistable("Uint32Array"); declareTypedArrayPersistable("Uint16Array"); declareTypedArrayPersistable("Uint8Array"); declareTypedArrayPersistable("Int32Array"); declareTypedArrayPersistable("Int16Array"); declareTypedArrayPersistable("Int8Array"); /** * returns true if the property is persistable. * @param obj * @param propertyName * @returns {boolean} */ function isPropertyPersistable(obj, propertyName, options) { if (!obj.hasOwnProperty(propertyName)) { return false; } if (propertyName === '____index') { return false; } if (options && options.ignored) { for (var i = 0; i < options.ignored.length; i++) { var o = options.ignored[i]; if (typeof o === "string") { if (o === propertyName) { return false; } } else if (o instanceof RegExp) { if (propertyName.match(o)) { return false; } } } } return true; } function find_object(context, obj) { if (obj.____index !== undefined) { assert(context.objects[obj.____index] === obj); return obj.____index; } return -1; } function add_object_in_index(context, obj, serializing_data) { var id = context.index.length; obj.____index = id; context.index.push(serializing_data); context.objects.push(obj); return id; } function extract_object_classname(object) { var className = object.constructor.name; if (className) { return className; } /* in some old version of node className could be null */ // istanbul ignore next if (true) { if (object instanceof Float32Array) { return "Float32Array"; } if (object instanceof Uint32Array) { return "Uint32Array"; } if (object instanceof Uint16Array) { return "Uint16Array"; } if (object instanceof Uint8Array) { return "Uint8Array"; } if (object instanceof Int32Array) { return "Int32Array"; } if (object instanceof Int16Array) { return "Int16Array"; } if (object instanceof Int8Array) { return "Int8Array"; } } } function _serialize_xxx(context, options, object, construct, serialize) { // check if the object has already been serialized let id = find_object(context, object); if (id === -1) { const stuff = construct(); id = add_object_in_index(context, object, stuff); serialize(context, object, stuff, options); } return id; } function _deserialize_xxx(context, object_id, contruct, deserialize) { assert(object_id); // check if this object has already been de-serialized if (context.cache[object_id] !== undefined) { return context.cache[object_id]; } const newStuff = contruct(); context.cache[object_id] = newStuff; const serializing_data = context.index[object_id]; deserialize(context, newStuff, serializing_data); return newStuff; } function _serialize_map(context, options, object) { return _serialize_xxx(context, options, object, () => [], (context, object, mapJson, options) => { for (const [key, value] of object.entries()) { mapJson.push([ _serialize(context, options, key), _serialize(context, options, value), ]) } }); } function _deserialize_map(context, object_id) { return _deserialize_xxx(context, object_id, () => new Map(), (context, newMap, serializing_data) => { for (const [key, value] of serializing_data) { const k = deserialize_node_or_value(context, key); const v = deserialize_node_or_value(context, value); newMap.set(k, v); } }) } function _serialize_set(context, options, object) { return _serialize_xxx(context, options, object, () => [], (context, object, setJson, options) => { for (const value of object.values()) { setJson.push(_serialize(context, options, value)) } }); } function _deserialize_set(context, object_id) { return _deserialize_xxx(context, object_id, () => new Set(), (context, newSet, serializing_data) => { for (const value of serializing_data) { const v = deserialize_node_or_value(context, value); newSet.add(v) } }) } function _serialize_basic_object(context, options, object) { const className = extract_object_classname(object); // istanbul ignore next if (className !== "Object" && !g_classInfos.hasOwnProperty(className)) { console.log(object); throw new Error("class " + className + " is not registered in class Factory - deserialization will not be possible"); } return _serialize_xxx(context, options, object, () => ({ c: className }), (context, object, s, options) => g_classInfos[className].serializeFunc(context, object, s, options)); } function _deserialize_basic_object(context, object_id) { if (object_id === null) { return null; } // check if this object has already been de-serialized if (context.cache[object_id] !== undefined) { return context.cache[object_id]; } var serializing_data = context.index[object_id]; var cache_object = _deserialize_object(context, serializing_data, object_id); assert(context.cache[object_id] === cache_object); return cache_object; } function _serialize_object(context, options, serializingObject, object) { assert(context); assert(object !== undefined); if (object === null) { serializingObject.o = null; return; } const className = extract_object_classname(object); // j => json object to follow // d => date // a => array // o => class { c: className d: data } // o => null // @ => already serialized object // s => Set // m => Map if (className === "Array") { serializingObject.a = object.map(_serialize.bind(null, context, options)); } else if (object.constructor === Map) { serializingObject.m = _serialize_map(context, options, object); } else if (object.constructor === Set) { serializingObject.s = _serialize_set(context, options, object); } else if (className === "Date") { serializingObject.d = object.getTime(); } else { serializingObject.o = _serialize_basic_object(context, options, object); } } function _serialize(context, options, object) { assert(context); // istanbul ignore next if (object === undefined) { return undefined; } var serializingObject = {}; var _throw = function () { throw new Error("invalid typeof " + typeof object + " " + JSON.stringify(object, null, " ")); } switch (typeof object) { case 'number': case 'boolean': case 'string': // basic type return object; case 'object': _serialize_object(context, options, serializingObject, object); break; default: if (options.errorHandler) { options.errorHandler(context, options, object, _throw) } else { _throw() } } return serializingObject; } /** * * @param object {object} object to serialize * @param [options] {object} optional options * @param [options.ignored] {string|regexp|Array<string|regexp>} pattern for field to not serialize * @return {string} */ function serialize(object, options) { assert(object !== undefined, "serialize: expect a valid object to serialize "); var context = { index: [], objects: [] }; var obj = _serialize(context, options, object); // unset temporary ___index properties context.objects.forEach(function (e) { delete e.____index; }); return JSON.stringify([context.index, obj]);// ,null," "); } function deserialize_node(context, node) { assert(context); // special treatment if (node === null || node === undefined) { return node; } if (node.hasOwnProperty("s")) { return _deserialize_set(context, node.s); } else if (node.hasOwnProperty("m")) { return _deserialize_map(context, node.m); } else if (node.hasOwnProperty("d")) { return new Date(node.d); } else if (node.hasOwnProperty("o")) { return _deserialize_basic_object(context, node.o); } else if (node.hasOwnProperty("a")) { // return _deserialize_object(node.o); return node.a.map(deserialize_node_or_value.bind(null, context)); } // istanbul ignore next throw new Error("Unsupported deserialize_node " + JSON.stringify(node)); } function _deserialize_object(context, object_definition, object_id) { assert(object_definition.c); var className = object_definition.c; var classInfo = g_classInfos[className]; // istanbul ignore next if (!classInfo) { throw new Error(" Cannot find constructor to deserialize class of type " + className + ". use declarePersistable(Constructor)"); } var constructor = classInfo.constructor; assert(isFunction(constructor)); var obj = classInfo.deserializeFunc(context, object_id, object_definition); if (constructor && constructor.serialijseOptions) { // onDeserialize is called immediately after object has been created if (constructor.serialijseOptions.onDeserialize) { constructor.serialijseOptions.onDeserialize(obj); } // onPostDeserialize call is postponed after the main object has been fully de-serializednpm if (constructor.serialijseOptions.onPostDeserialize) { context.postDeserialiseActions.push(obj); } } return obj; } function deserialize(serializationString) { var data; if (typeof serializationString === 'string') { data = JSON.parse(serializationString); } else if (typeof serializationString === 'object') { data = serializationString; } // istanbul ignore next if (!(data instanceof Array)) { throw new Error("Invalid Serialization data"); } // istanbul ignore next if (data.length !== 2) { throw new Error("Invalid Serialization data"); } var rawObject = data[1]; var context = { index: data[0], cache: [], postDeserialiseActions: [] }; var deserializedObject = deserialize_node_or_value(context, rawObject); context.postDeserialiseActions.forEach(function (o) { o.constructor.serialijseOptions.onPostDeserialize(o); }); return deserializedObject; } exports.deserialize = deserialize; exports.serialize = serialize; exports.declarePersistable = declarePersistable; exports.serializeZ = function (obj, callback) { var zlib = require("zlib"); var str = serialize(obj); zlib.deflate(str, function (err, buff) { // istanbul ignore next if (err) { return callback(err); } callback(null, buff); }); }; exports.deserializeZ = function (data, callback) { var zlib = require("zlib"); zlib.inflate(data, function (err, buff) { // istanbul ignore next if (err) { return callback(err); } callback(null, deserialize(buff.toString())); }); }; })(typeof exports === 'undefined' ? this['serialijse'] = {} : exports);