UNPKG

@deja-js/json-object-mapper

Version:

A TypeScript library to serialize and deserialize JSON objects in a fast and non-recursive way

603 lines (594 loc) 24.3 kB
import 'reflect-metadata'; function __decorate(decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; } function __metadata(k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); } /** * Returns the JsonProperty decorator metadata. */ var getJsonPropertyDecoratorMetadata = function (target, key) { return Reflect.getMetadata(JSON_PROPERTY_DECORATOR_NAME, target, key); }; /** * Returns the JsonProperty name (if any) associated with the object instance. * If any JsonProperty metadata found, it returns the key name as the name of the property. */ var getKeyName = function (target, key) { var metadata = getJsonPropertyDecoratorMetadata(target, key); // tslint:disable-next-line:triple-equals if (metadata != undefined && metadata.name != undefined) { return metadata.name; } else { return key; } }; /** * Returns the JsonPropertyDecoratorMetadata for the property */ var getJsonPropertyDecorator = function (metadata) { return getPropertyDecorator(JSON_PROPERTY_DECORATOR_NAME, metadata); }; var getPropertyDecorator = function (metadataKey, metadata) { return Reflect.metadata(metadataKey, metadata); }; /** * Checks to see if the specified type is a standard JS object type. */ var isSimpleType = function (typeName) { switch (typeName) { case Constants.STRING_TYPE: return true; case Constants.NUMBER_TYPE: return true; case Constants.BOOLEAN_TYPE: return true; case Constants.DATE_TYPE: return true; case Constants.STRING_TYPE_LOWERCASE: return true; case Constants.NUMBER_TYPE_LOWERCASE: return true; case Constants.BOOLEAN_TYPE_LOWERCASE: return true; case Constants.DATE_TYPE_LOWERCASE: return true; default: return false; } }; /** * Returns the the instance type name by looking at the constructor name. * Stupid IE does not have name property! Hence the hack. */ var getTypeNameFromInstance = function (instance) { return instance.toString().trim().split(/[\s\()]/g)[1]; }; var getType = function (instance, key) { return Reflect.getMetadata('design:type', instance, key); }; var isArrayType = function (instance, key) { return Array === getType(instance, key); }; var getTypeName = function (instance, key) { var type = getType(instance, key); // tslint:disable-next-line:triple-equals if (type != undefined) { return getTypeNameFromInstance(type); } return type; }; var Constants = { OBJECT_TYPE: 'Object', OBJECT_TYPE_LOWERCASE: 'object', STRING_TYPE: 'String', STRING_TYPE_LOWERCASE: 'string', NUMBER_TYPE: 'Number', NUMBER_TYPE_LOWERCASE: 'number', BOOLEAN_TYPE: 'Boolean', BOOLEAN_TYPE_LOWERCASE: 'boolean', DATE_TYPE: 'Date', DATE_TYPE_LOWERCASE: 'date', ARRAY_TYPE: 'Array', ARRAY_TYPE_LOWERCASE: 'array', FROM_ARRAY: 'fromArray' }; var getCachedType = function (type, cache) { // tslint:disable-next-line:triple-equals var typeName = type.getJsonObjectMapperCacheKey != undefined ? type.getJsonObjectMapperCacheKey() : getTypeNameFromInstance(type); if (!cache[typeName]) { cache[typeName] = new type(); } return cache[typeName]; }; /** * Decorator names */ var JSON_PROPERTY_DECORATOR_NAME = 'JsonProperty'; var AccessType; (function (AccessType) { AccessType[AccessType["READ_ONLY"] = 0] = "READ_ONLY"; AccessType[AccessType["WRITE_ONLY"] = 1] = "WRITE_ONLY"; AccessType[AccessType["BOTH"] = 2] = "BOTH"; })(AccessType || (AccessType = {})); /** * JsonProperty Decorator function. */ var JsonProperty = function (metadata) { if (typeof metadata === 'string') { return getJsonPropertyDecorator({ name: metadata, required: false, access: AccessType.BOTH }); } else { return getJsonPropertyDecorator(metadata); } }; /** * Decorator for specifying cache key. * Used for Serializer/Deserializer caching. * * @export * @param {string} key * @returns */ var CacheKey = function (key) { return function (f) { var functionName = 'getJsonObjectMapperCacheKey'; var functionImpl = new Function("return '" + key + "';"); f[functionName] = functionImpl; }; }; /** * Json convertion error type. */ var JsonConverstionError = (function () { function JsonConverstionError(message, json) { this.json = json; this.message = message; this.stack = (new Error()).stack; } return JsonConverstionError; }()); var SimpleTypeCoverter = function (value, type) { return type === Constants.DATE_TYPE ? new Date(value) : value; }; /** * Deserializes a standard js object type(string, number and boolean) from json. */ var DeserializeSimpleType = function (instance, instanceKey, type, json, jsonKey) { try { instance[instanceKey] = json[jsonKey]; return []; } catch (e) { // tslint:disable-next-line:no-string-literal throw new JsonConverstionError("Property '" + instanceKey + "' of " + instance.constructor['name'] + " does not match datatype of " + jsonKey, json); } }; /** * Deserializes a standard js Date object type from json. */ var DeserializeDateType = function (instance, instanceKey, type, json, jsonKey) { try { instance[instanceKey] = new Date(json[jsonKey]); return []; } catch (e) { // tslint:disable-next-line:no-string-literal throw new JsonConverstionError("Property '" + instanceKey + "' of " + instance.constructor['name'] + " does not match datatype of " + jsonKey, json); } }; /** * Deserializes a JS array type from json. */ var DeserializeArrayType = function (instance, instanceKey, type, json, jsonKey) { // tslint:disable-next-line:triple-equals var jsonObject = jsonKey != undefined ? json[jsonKey] : json; var jsonArraySize = jsonObject.length; var conversionFunctionsList = new Array(); if (jsonArraySize > 0) { var arrayInstance = []; instance[instanceKey] = arrayInstance; for (var i = 0; i < jsonArraySize; i++) { var typeName = getTypeNameFromInstance(type); if (!isSimpleType(typeName)) { var typeInstance = new type(); conversionFunctionsList.push({ functionName: Constants.OBJECT_TYPE, instance: typeInstance, json: jsonObject[i] }); arrayInstance.push(typeInstance); } else { arrayInstance.push(conversionFunctions[Constants.FROM_ARRAY](jsonObject[i], typeName)); } } } return conversionFunctionsList; }; /** * Deserializes a js object type from json. */ var DeserializeComplexType = function (instance, instanceKey, type, json, jsonKey) { var conversionFunctionsList = new Array(); var objectInstance; /** * If instanceKey is not passed on then it's the first iteration of the functions. */ // tslint:disable-next-line:triple-equals if (instanceKey != undefined) { objectInstance = new type(); instance[instanceKey] = objectInstance; } else { objectInstance = instance; } Object.keys(objectInstance).forEach(function (key) { /** * Check if there is any DecoratorMetadata attached to this property, otherwise create a new one. */ var metadata = getJsonPropertyDecoratorMetadata(objectInstance, key); if (metadata === undefined) { metadata = { name: key, required: false, access: AccessType.BOTH }; } // tslint:disable-next-line:triple-equals if (AccessType.WRITE_ONLY != metadata.access) { /** * Check requried property */ if (metadata.required && json[metadata.name] === undefined) { throw new JsonConverstionError("JSON structure does have have required property '" + metadata.name + "' as required by '" + getTypeNameFromInstance(objectInstance) + "[" + key + "]", json); } // tslint:disable-next-line:triple-equals var jsonKeyName = metadata.name != undefined ? metadata.name : key; // tslint:disable-next-line:triple-equals if (json[jsonKeyName] != undefined) { /** * If metadata has deserializer, use that one instead. */ // tslint:disable-next-line:triple-equals if (metadata.deserializer != undefined) { objectInstance[key] = getOrCreateDeserializer(metadata.deserializer).deserialize(json[jsonKeyName]); } else if (metadata.type === undefined) { /** * If we do not have any type defined, then we can't do much here but to hope for the best. */ objectInstance[key] = json[jsonKeyName]; } else { if (!isArrayType(objectInstance, key)) { // tslint:disable-next-line:triple-equals var typeName = metadata.type != undefined ? getTypeNameFromInstance(metadata.type) : getTypeName(objectInstance, key); if (!isSimpleType(typeName)) { objectInstance[key] = new metadata.type(); conversionFunctionsList.push({ functionName: Constants.OBJECT_TYPE, type: metadata.type, instance: objectInstance[key], json: json[jsonKeyName] }); } else { conversionFunctions[typeName](objectInstance, key, typeName, json, jsonKeyName); } } else { var moreFunctions = conversionFunctions[Constants.ARRAY_TYPE](objectInstance, key, metadata.type, json, jsonKeyName); moreFunctions.forEach(function (struct) { conversionFunctionsList.push(struct); }); } } } } }); return conversionFunctionsList; }; /** * Object to cache deserializers */ var deserializers = new Object(); /** * Checks to see if the deserializer already exists or not. * If not, creates a new one and caches it, returns the * cached instance otherwise. */ var getOrCreateDeserializer = function (type) { return getCachedType(type, deserializers); }; /** * List of JSON object conversion functions. */ var conversionFunctions = new Object(); conversionFunctions[Constants.OBJECT_TYPE] = DeserializeComplexType; conversionFunctions[Constants.ARRAY_TYPE] = DeserializeArrayType; conversionFunctions[Constants.DATE_TYPE] = DeserializeDateType; conversionFunctions[Constants.STRING_TYPE] = DeserializeSimpleType; conversionFunctions[Constants.NUMBER_TYPE] = DeserializeSimpleType; conversionFunctions[Constants.BOOLEAN_TYPE] = DeserializeSimpleType; conversionFunctions[Constants.FROM_ARRAY] = SimpleTypeCoverter; conversionFunctions[Constants.OBJECT_TYPE_LOWERCASE] = DeserializeComplexType; conversionFunctions[Constants.ARRAY_TYPE_LOWERCASE] = DeserializeArrayType; conversionFunctions[Constants.DATE_TYPE_LOWERCASE] = DeserializeDateType; conversionFunctions[Constants.STRING_TYPE_LOWERCASE] = DeserializeSimpleType; conversionFunctions[Constants.NUMBER_TYPE_LOWERCASE] = DeserializeSimpleType; conversionFunctions[Constants.BOOLEAN_TYPE_LOWERCASE] = DeserializeSimpleType; var SerializeArrayType = function (parentStructure, instanceStructure, instanceIndex) { var furtherSerializationStructures = new Object(); var arrayInstance = instanceStructure.instance; instanceStructure.visited = true; arrayInstance.forEach(function (value) { // tslint:disable-next-line:triple-equals if (value != undefined) { if (!isSimpleType(typeof value)) { var struct = { id: uniqueId(), type: Constants.OBJECT_TYPE, instance: value, parentIndex: instanceIndex, values: new Array(), key: undefined, visited: false }; furtherSerializationStructures[struct.id] = struct; } else { instanceStructure.values.push(serializeFunctions[typeof value](undefined, value, serializers[typeof value])); } } }); return createArrayOfSerializationStructures(furtherSerializationStructures); }; var createArrayOfSerializationStructures = function (serializationStructuresObject) { var serializationStructures = new Array(); Object.keys(serializationStructuresObject).forEach(function (key) { serializationStructures.push(serializationStructuresObject[key]); }); return serializationStructures; }; var serializeObject = function (key, instanceValuesStack) { // tslint:disable-next-line:triple-equals var json = (key != undefined ? "\"" + key + "\":" : ''); return json + "{" + instanceValuesStack.join() + "}"; }; var serializeArray = function (key, instanceValuesStack) { // tslint:disable-next-line:triple-equals var json = (key != undefined ? "\"" + key + "\":" : ''); return json + "[" + instanceValuesStack.join() + "]"; }; var mergeObjectOrArrayValuesAndCopyToParents = function (instanceStructure, parentStructure) { mergeObjectOrArrayValues(instanceStructure); parentStructure.values.push(instanceStructure.values.pop()); }; var mergeObjectOrArrayValues = function (instanceStructure) { var mergedValue; if (instanceStructure.type === Constants.OBJECT_TYPE) { mergedValue = serializeObject(instanceStructure.key, instanceStructure.values); } else { mergedValue = serializeArray(instanceStructure.key, instanceStructure.values); } instanceStructure.values = []; instanceStructure.values.push(mergedValue); }; var SerializeObjectType = function (parentStructure, instanceStructure, instanceIndex) { var furtherSerializationStructures = new Object(); instanceStructure.visited = true; Object.keys(instanceStructure.instance).forEach(function (key) { var keyInstance = instanceStructure.instance[key]; // tslint:disable-next-line:triple-equals if (keyInstance != undefined) { var metadata = getJsonPropertyDecoratorMetadata(instanceStructure.instance, key); // tslint:disable-next-line:triple-equals if (metadata != undefined && AccessType.READ_ONLY === metadata.access) { } else if (metadata != undefined && metadata.serializer != undefined) { var serializer = getOrCreateSerializer(metadata.serializer); instanceStructure.values.push(serializeFunctions[Constants.STRING_TYPE](getKeyName(instanceStructure.instance, key), keyInstance, serializer)); } else { if (keyInstance instanceof Array) { var struct = { id: uniqueId(), type: Constants.ARRAY_TYPE, instance: keyInstance, parentIndex: instanceIndex, values: new Array(), key: getKeyName(instanceStructure.instance, key), visited: false }; furtherSerializationStructures[struct.id] = struct; } else if (!isSimpleType(typeof keyInstance)) { var struct = { id: uniqueId(), type: Constants.OBJECT_TYPE, instance: keyInstance, parentIndex: instanceIndex, values: new Array(), key: getKeyName(instanceStructure.instance, key), visited: false }; furtherSerializationStructures[struct.id] = struct; } else { var serializer = serializers[typeof keyInstance]; instanceStructure.values.push(serializeFunctions[typeof keyInstance](getKeyName(instanceStructure.instance, key), keyInstance, serializer)); } } } }); return createArrayOfSerializationStructures(furtherSerializationStructures); }; /** * Serialize any type with key value pairs */ var SerializeSimpleType = function (key, instance, serializer) { var value = serializer.serialize(instance); // tslint:disable-next-line:triple-equals if (key != undefined) { return "\"" + key + "\":" + value; } else { return value; } }; var DateSerializer = (function () { function DateSerializer() { this.serialize = function (value) { return value.getTime(); }; } DateSerializer = __decorate([ CacheKey('DateSerializer'), __metadata('design:paramtypes', []) ], DateSerializer); return DateSerializer; }()); var StringSerializer = (function () { function StringSerializer() { this.serialize = function (value) { return JSON.stringify(value); }; } StringSerializer = __decorate([ CacheKey('StringSerializer'), __metadata('design:paramtypes', []) ], StringSerializer); return StringSerializer; }()); var NumberSerializer = (function () { function NumberSerializer() { this.serialize = function (value) { return value; }; } NumberSerializer = __decorate([ CacheKey('NumberSerializer'), __metadata('design:paramtypes', []) ], NumberSerializer); return NumberSerializer; }()); var BooleanSerializer = (function () { function BooleanSerializer() { this.serialize = function (value) { return value; }; } BooleanSerializer = __decorate([ CacheKey('BooleanSerializer'), __metadata('design:paramtypes', []) ], BooleanSerializer); return BooleanSerializer; }()); /** * Object to cache serializers */ var serializers = new Object(); serializers[Constants.STRING_TYPE] = new StringSerializer(); serializers[Constants.NUMBER_TYPE] = new NumberSerializer(); serializers[Constants.DATE_TYPE] = new DateSerializer(); serializers[Constants.BOOLEAN_TYPE] = new BooleanSerializer(); serializers[Constants.STRING_TYPE_LOWERCASE] = serializers[Constants.STRING_TYPE]; serializers[Constants.NUMBER_TYPE_LOWERCASE] = serializers[Constants.NUMBER_TYPE]; serializers[Constants.DATE_TYPE_LOWERCASE] = serializers[Constants.DATE_TYPE]; serializers[Constants.BOOLEAN_TYPE_LOWERCASE] = serializers[Constants.BOOLEAN_TYPE]; /** * Checks to see if the serializer already exists or not. * If not, creates a new one and caches it, returns the * cached instance otherwise. */ var getOrCreateSerializer = function (type) { return getCachedType(type, serializers); }; var serializeFunctions = []; serializeFunctions[Constants.STRING_TYPE] = SerializeSimpleType; serializeFunctions[Constants.NUMBER_TYPE] = SerializeSimpleType; serializeFunctions[Constants.BOOLEAN_TYPE] = SerializeSimpleType; serializeFunctions[Constants.DATE_TYPE] = SerializeSimpleType; serializeFunctions[Constants.ARRAY_TYPE] = SerializeArrayType; serializeFunctions[Constants.OBJECT_TYPE] = SerializeObjectType; serializeFunctions[Constants.STRING_TYPE_LOWERCASE] = SerializeSimpleType; serializeFunctions[Constants.NUMBER_TYPE_LOWERCASE] = SerializeSimpleType; serializeFunctions[Constants.BOOLEAN_TYPE_LOWERCASE] = SerializeSimpleType; serializeFunctions[Constants.DATE_TYPE_LOWERCASE] = SerializeSimpleType; serializeFunctions[Constants.ARRAY_TYPE_LOWERCASE] = SerializeArrayType; serializeFunctions[Constants.OBJECT_TYPE_LOWERCASE] = SerializeObjectType; var uniqueId = function () { return Math.random() + "-" + Date.now(); }; var ObjectMapper; (function (ObjectMapper) { /** * Deserializes an array of object types with the passed on JSON data. */ ObjectMapper.deserializeArray = function (type, json) { var ObjectsArrayParent = (function () { function ObjectsArrayParent() { this.instances = undefined; } return ObjectsArrayParent; }()); var parent = new ObjectsArrayParent(); runDeserialization(conversionFunctions[Constants.ARRAY_TYPE](parent, 'instances', type, json, undefined)); return parent.instances; }; /** * Deserializes a Object type with the passed on JSON data. */ ObjectMapper.deserialize = function (type, json) { var dtoInstance = new type(); var conversionFunctionStructure = { functionName: Constants.OBJECT_TYPE, instance: dtoInstance, json: json, }; runDeserialization([conversionFunctionStructure]); return dtoInstance; }; var runDeserialization = function (conversionFunctionStructures) { var converstionFunctionsArray = new Array(); conversionFunctionStructures.forEach(function (struct) { converstionFunctionsArray.push(struct); }); var conversionFunctionStructure = converstionFunctionsArray[0]; // tslint:disable-next-line:triple-equals while (conversionFunctionStructure != undefined) { var stackEntries = conversionFunctions[conversionFunctionStructure.functionName](conversionFunctionStructure.instance, conversionFunctionStructure.instanceKey, conversionFunctionStructure.type, conversionFunctionStructure.json, conversionFunctionStructure.jsonKey); stackEntries.forEach(function (structure) { converstionFunctionsArray.push(structure); }); conversionFunctionStructure = converstionFunctionsArray.pop(); } }; /** * Serializes an object instance to JSON string. */ ObjectMapper.serialize = function (obj) { var stack = new Array(); var struct = { id: undefined, type: Array.isArray(obj) === true ? Constants.ARRAY_TYPE : Constants.OBJECT_TYPE, instance: obj, parentIndex: undefined, values: new Array(), key: undefined, visited: false }; stack.push(struct); do { var instanceStruct = stack[stack.length - 1]; var parentStruct = stack[stack.length > 1 ? instanceStruct.parentIndex : 0]; if (instanceStruct.visited) { mergeObjectOrArrayValuesAndCopyToParents(instanceStruct, parentStruct); stack.pop(); } else { var moreStructures = serializeFunctions[instanceStruct.type](parentStruct, instanceStruct, stack.length - 1); if (moreStructures.length > 0) { var index = moreStructures.length; while (--index >= 0) { stack.push(moreStructures[index]); } } else { if (stack.length > 1) { mergeObjectOrArrayValuesAndCopyToParents(instanceStruct, parentStruct); } stack.pop(); } } } while (stack.length > 1); mergeObjectOrArrayValues(struct); return struct.values[0]; }; })(ObjectMapper || (ObjectMapper = {})); export { ObjectMapper, JsonProperty, JsonConverstionError, AccessType, CacheKey, DateSerializer };