UNPKG

json-object-mapper

Version:

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

695 lines (684 loc) 27.3 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); 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); } /** * Reflect Metadata json properties storage name. */ var METADATA_JSON_PROPERTIES_NAME = "JsonProperties"; var METADATA_JSON_IGNORE_NAME = "JsonIgnore"; /** * 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); }; /** * Returns the JsonIgnoreDecoratorMetadata for the property */ var getJsonIgnoreDecorator = function () { return function (target, propertyKey) { Reflect.defineMetadata(METADATA_JSON_IGNORE_NAME, true, target, propertyKey); }; }; 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 extractClassname = function (instance) { var instr = instance.toString(); var i1 = instr.indexOf("class"); var i2 = instr.indexOf("{", i1 + 5); return instr.substring(i1 + 5, i2).trim(); }; var getTypeNameFromInstance = function (instance) { return instance.name || extractClassname(instance); }; 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"; (function (AccessType) { AccessType[AccessType["READ_ONLY"] = 0] = "READ_ONLY"; AccessType[AccessType["WRITE_ONLY"] = 1] = "WRITE_ONLY"; AccessType[AccessType["BOTH"] = 2] = "BOTH"; })(exports.AccessType || (exports.AccessType = {})); /** * JsonProperty Decorator function. */ var JsonProperty = function (metadata) { if (typeof metadata === "string") { return getJsonPropertyDecorator({ name: metadata, required: false, access: exports.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; }; }; /** * JsonIgnore Decorator function. */ var JsonIgnore = function () { return getJsonIgnoreDecorator(); }; /** * 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, config) { var jsonObject = jsonKey !== undefined ? json[jsonKey] || [] : json; var jsonArraySize = jsonObject.length; var conversionFunctionsList = []; var arrayInstance = []; instance[instanceKey] = arrayInstance; if (jsonArraySize > 0) { for (var i = 0; i < jsonArraySize; i++) { if (jsonObject[i]) { var typeName = getTypeNameFromInstance(type); if (!isSimpleType(typeName)) { var typeInstance = new type(); conversionFunctionsList.push({ functionName: Constants.OBJECT_TYPE, instance: typeInstance, json: jsonObject[i], undefined, config, }); 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, config) { var conversionFunctionsList = []; 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; } var objectKeys = Object.keys(objectInstance); objectKeys = objectKeys.concat((Reflect.getMetadata(METADATA_JSON_PROPERTIES_NAME, objectInstance) || []).filter(function (item) { if (objectInstance.constructor.prototype.hasOwnProperty(item) && Object.getOwnPropertyDescriptor(objectInstance.constructor.prototype, item).set === undefined) { // Property does not have setter return false; } return objectKeys.indexOf(item) < 0; })); objectKeys = objectKeys.filter(function (item) { return !Reflect.hasMetadata(METADATA_JSON_IGNORE_NAME, objectInstance, item); }); objectKeys.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: exports.AccessType.BOTH }; } // tslint:disable-next-line:triple-equals if (exports.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 = config.ignoreNameMetadata === true ? key : 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], jsonKeyName, config, }); } 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 = {}; /** * 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 = {}; 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 = {}; var arrayInstance = instanceStructure.instance; instanceStructure.visited = true; arrayInstance.forEach(function (value) { if (value !== undefined) { if (!isSimpleType(typeof value)) { var struct = { id: uniqueId(), type: Constants.OBJECT_TYPE, instance: value, parentIndex: instanceIndex, values: [], 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 = []; Object.keys(serializationStructuresObject).forEach(function (key) { serializationStructures.push(serializationStructuresObject[key]); }); return serializationStructures; }; var serializeObject = function (key, instanceValuesStack) { var json = key !== undefined ? "\"" + key + "\":" : ""; return json + "{" + instanceValuesStack.join() + "}"; }; var serializeArray = function (key, instanceValuesStack) { 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 = {}; instanceStructure.visited = true; var objectKeys = Object.keys(instanceStructure.instance); objectKeys = objectKeys.concat((Reflect.getMetadata(METADATA_JSON_PROPERTIES_NAME, instanceStructure.instance) || []).filter(function (item) { if (instanceStructure.instance.constructor.prototype.hasOwnProperty(item) && Object.getOwnPropertyDescriptor(instanceStructure.instance.constructor.prototype, item).get === undefined) { // Property does not have getter return false; } return objectKeys.indexOf(item) < 0; })); objectKeys = objectKeys.filter(function (item) { return !Reflect.hasMetadata(METADATA_JSON_IGNORE_NAME, instanceStructure.instance, item); }); objectKeys.forEach(function (key) { var keyInstance = instanceStructure.instance[key]; if (keyInstance === null) { instanceStructure.values.push("\"" + key + "\":" + keyInstance); } else if (keyInstance !== undefined) { var metadata = getJsonPropertyDecoratorMetadata(instanceStructure.instance, key); if (metadata !== undefined && exports.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: [], 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: [], 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); 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 = {}; 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(); }; (function (ObjectMapper) { /** * Deserializes an array of object types with the passed on JSON data. */ ObjectMapper.deserializeArray = function (type, json, config) { if (config === void 0) { config = { ignoreNameMetadata: false }; } var ObjectsArrayParent = (function () { function ObjectsArrayParent() { this.instances = undefined; } return ObjectsArrayParent; }()); var parent = new ObjectsArrayParent(); runDeserialization(conversionFunctions[Constants.ARRAY_TYPE](parent, "instances", type, json, undefined, config), config); return parent.instances; }; /** * Deserializes a Object type with the passed on JSON data. */ ObjectMapper.deserialize = function (type, json, config) { if (config === void 0) { config = { ignoreNameMetadata: false }; } var dtoInstance = new type(); var conversionFunctionStructure = { functionName: Constants.OBJECT_TYPE, instance: dtoInstance, json: json, }; runDeserialization([conversionFunctionStructure], config); return dtoInstance; }; var runDeserialization = function (conversionFunctionStructures, config) { var converstionFunctionsArray = []; conversionFunctionStructures.forEach(function (struct) { converstionFunctionsArray.push(struct); }); var conversionFunctionStructure = converstionFunctionsArray.length > 0 ? converstionFunctionsArray.pop() : undefined; // tslint:disable-next-line:triple-equals while (conversionFunctionStructure != undefined) { var stackEntries = conversionFunctions[conversionFunctionStructure.functionName](conversionFunctionStructure.instance, conversionFunctionStructure.instanceKey, conversionFunctionStructure.type, conversionFunctionStructure.json, conversionFunctionStructure.jsonKey, config); stackEntries.forEach(function (structure) { converstionFunctionsArray.push(structure); }); conversionFunctionStructure = converstionFunctionsArray.pop(); } }; /** * Serializes an object instance to JSON string. */ ObjectMapper.serialize = function (obj) { var stack = []; var struct = { id: undefined, type: Array.isArray(obj) === true ? Constants.ARRAY_TYPE : Constants.OBJECT_TYPE, instance: obj, parentIndex: undefined, values: [], 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]; }; })(exports.ObjectMapper || (exports.ObjectMapper = {})); exports.JsonProperty = JsonProperty; exports.JsonConverstionError = JsonConverstionError; exports.CacheKey = CacheKey; exports.JsonIgnore = JsonIgnore; exports.DateSerializer = DateSerializer; //# sourceMappingURL=ObjectMapper.js.map