From 8b027695f7f91bd4a120bb047d71cd2b5e056f02 Mon Sep 17 00:00:00 2001 From: zircon63 Date: Tue, 13 Nov 2018 19:00:07 +0300 Subject: [PATCH 1/2] initializing properties by from JSON in an instance --- src/main/index.ts | 209 +++++++++++++++++++++++----------------------- 1 file changed, 105 insertions(+), 104 deletions(-) diff --git a/src/main/index.ts b/src/main/index.ts index eafdea8..f856092 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -1,104 +1,105 @@ -import { conversionFunctions, ConversionFunctionStructure } from './DeserializationHelper'; -import { Constants } from './ReflectHelper'; -import { mergeObjectOrArrayValues, mergeObjectOrArrayValuesAndCopyToParents, SerializationStructure, serializeFunctions } from './SerializationHelper'; - -export namespace ObjectMapper { - - /** - * Deserializes an array of object types with the passed on JSON data. - */ - export const deserializeArray = (type: { new(): T }, json: Object): T[] => { - class ObjectsArrayParent { - instances: T[] = undefined; - } - - const parent: ObjectsArrayParent = 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. - */ - export const deserialize = (type: { new(): T }, json: Object): T => { - const dtoInstance = new type(); - const conversionFunctionStructure: ConversionFunctionStructure = { - functionName: Constants.OBJECT_TYPE, - instance: dtoInstance, - json: json, - }; - - runDeserialization([conversionFunctionStructure]); - - return dtoInstance; - }; - - const runDeserialization = (conversionFunctionStructures: ConversionFunctionStructure[]): void => { - - const converstionFunctionsArray: Array = []; - conversionFunctionStructures.forEach((struct: ConversionFunctionStructure) => { - converstionFunctionsArray.push(struct); - }); - - let conversionFunctionStructure: ConversionFunctionStructure = converstionFunctionsArray[0]; - - // tslint:disable-next-line:triple-equals - while (conversionFunctionStructure != undefined) { - const stackEntries: Array = conversionFunctions[conversionFunctionStructure.functionName]( - conversionFunctionStructure.instance, conversionFunctionStructure.instanceKey, - conversionFunctionStructure.type, conversionFunctionStructure.json, - conversionFunctionStructure.jsonKey); - stackEntries.forEach((structure: ConversionFunctionStructure) => { - converstionFunctionsArray.push(structure); - }); - conversionFunctionStructure = converstionFunctionsArray.pop(); - } - }; - - /** - * Serializes an object instance to JSON string. - */ - export const serialize = (obj: any): String => { - const stack: Array = []; - const struct: SerializationStructure = { - 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 { - const instanceStruct: SerializationStructure = stack[stack.length - 1]; - const parentStruct: SerializationStructure = stack[stack.length > 1 ? instanceStruct.parentIndex : 0]; - if (instanceStruct.visited) { - mergeObjectOrArrayValuesAndCopyToParents(instanceStruct, parentStruct); - stack.pop(); - } else { - const moreStructures: Array = serializeFunctions[instanceStruct.type](parentStruct, instanceStruct, stack.length - 1); - if (moreStructures.length > 0) { - let 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]; - }; -} -export { JsonProperty, JsonConverstionError, AccessType, CacheKey, JsonIgnore } from './DecoratorMetadata'; -export { DateSerializer } from './SerializationHelper'; +import { conversionFunctions, ConversionFunctionStructure } from './DeserializationHelper'; +import { Constants } from './ReflectHelper'; +import { mergeObjectOrArrayValues, mergeObjectOrArrayValuesAndCopyToParents, SerializationStructure, serializeFunctions } from './SerializationHelper'; + +export namespace ObjectMapper { + + /** + * Deserializes an array of object types with the passed on JSON data. + */ + export const deserializeArray = (type: { new(): T }, json: Object): T[] => { + class ObjectsArrayParent { + instances: T[] = undefined; + } + + const parent: ObjectsArrayParent = 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. + */ + export const deserialize = (type: { new(): T }, json: Object): T => { + const dtoInstance = new type(); + Object.keys(json).forEach((key: string) => dtoInstance[key] = undefined); + const conversionFunctionStructure: ConversionFunctionStructure = { + functionName: Constants.OBJECT_TYPE, + instance: dtoInstance, + json: json, + }; + + runDeserialization([conversionFunctionStructure]); + + return dtoInstance; + }; + + const runDeserialization = (conversionFunctionStructures: ConversionFunctionStructure[]): void => { + + const converstionFunctionsArray: Array = []; + conversionFunctionStructures.forEach((struct: ConversionFunctionStructure) => { + converstionFunctionsArray.push(struct); + }); + + let conversionFunctionStructure: ConversionFunctionStructure = converstionFunctionsArray[0]; + + // tslint:disable-next-line:triple-equals + while (conversionFunctionStructure != undefined) { + const stackEntries: Array = conversionFunctions[conversionFunctionStructure.functionName]( + conversionFunctionStructure.instance, conversionFunctionStructure.instanceKey, + conversionFunctionStructure.type, conversionFunctionStructure.json, + conversionFunctionStructure.jsonKey); + stackEntries.forEach((structure: ConversionFunctionStructure) => { + converstionFunctionsArray.push(structure); + }); + conversionFunctionStructure = converstionFunctionsArray.pop(); + } + }; + + /** + * Serializes an object instance to JSON string. + */ + export const serialize = (obj: any): String => { + const stack: Array = []; + const struct: SerializationStructure = { + 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 { + const instanceStruct: SerializationStructure = stack[stack.length - 1]; + const parentStruct: SerializationStructure = stack[stack.length > 1 ? instanceStruct.parentIndex : 0]; + if (instanceStruct.visited) { + mergeObjectOrArrayValuesAndCopyToParents(instanceStruct, parentStruct); + stack.pop(); + } else { + const moreStructures: Array = serializeFunctions[instanceStruct.type](parentStruct, instanceStruct, stack.length - 1); + if (moreStructures.length > 0) { + let 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]; + }; +} +export { JsonProperty, JsonConverstionError, AccessType, CacheKey, JsonIgnore } from './DecoratorMetadata'; +export { DateSerializer } from './SerializationHelper'; From da55dfe6827622a4ea4d09e56ea9ea823fc72b90 Mon Sep 17 00:00:00 2001 From: zircon63 Date: Tue, 13 Nov 2018 19:27:32 +0300 Subject: [PATCH 2/2] initializing properties by from JSON in an instance --- src/main/DeserializationHelper.ts | 379 +++++++++++++++--------------- src/main/index.ts | 1 - 2 files changed, 190 insertions(+), 190 deletions(-) diff --git a/src/main/DeserializationHelper.ts b/src/main/DeserializationHelper.ts index 629cc77..4e575c0 100644 --- a/src/main/DeserializationHelper.ts +++ b/src/main/DeserializationHelper.ts @@ -1,189 +1,190 @@ -import { AccessType, Deserializer, JsonConverstionError , JsonPropertyDecoratorMetadata} from './DecoratorMetadata'; -import { Constants, getCachedType, getJsonPropertyDecoratorMetadata, getTypeName, getTypeNameFromInstance, isArrayType, isSimpleType, METADATA_JSON_IGNORE_NAME, METADATA_JSON_PROPERTIES_NAME } from './ReflectHelper'; - -declare var Reflect; - -const SimpleTypeCoverter = (value: any, type: string): any => { - return type === Constants.DATE_TYPE ? new Date(value) : value; -}; - -/** - * Deserializes a standard js object type(string, number and boolean) from json. - */ -export const DeserializeSimpleType = (instance: Object, instanceKey: string, type: any, json: any, jsonKey: string) => { - 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. - */ -export const DeserializeDateType = (instance: Object, instanceKey: string, type: any, json: any, jsonKey: string): Array => { - 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. - */ -export let DeserializeArrayType = (instance: Object, instanceKey: string, type: any, json: Object, jsonKey: string): Array => { - const jsonObject = (jsonKey !== undefined) ? (json[jsonKey] || []) : json; - const jsonArraySize = jsonObject.length; - const conversionFunctionsList = []; - const arrayInstance = []; - instance[instanceKey] = arrayInstance; - if (jsonArraySize > 0) { - for (let i = 0; i < jsonArraySize; i++) { - if (jsonObject[i]) { - const typeName = getTypeNameFromInstance(type); - if (!isSimpleType(typeName)) { - const 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. - */ -export const DeserializeComplexType = (instance: Object, instanceKey: string, type: any, json: any, jsonKey: string): Array => { - const conversionFunctionsList = []; - - let 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; - } - - let objectKeys: string[] = Object.keys(objectInstance); - objectKeys = objectKeys.concat((Reflect.getMetadata(METADATA_JSON_PROPERTIES_NAME, objectInstance) || []).filter((item: string) => { - 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((item: string) => { - return !Reflect.hasMetadata(METADATA_JSON_IGNORE_NAME, objectInstance, item); - }); - objectKeys.forEach((key: string) => { - /** - * Check if there is any DecoratorMetadata attached to this property, otherwise create a new one. - */ - let metadata: JsonPropertyDecoratorMetadata = 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 - const 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 - const 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 { - const moreFunctions: Array = conversionFunctions[Constants.ARRAY_TYPE](objectInstance, key, metadata.type, json, jsonKeyName); - moreFunctions.forEach((struct: ConversionFunctionStructure) => { - conversionFunctionsList.push(struct); - }); - } - } - } - } - - }); - - return conversionFunctionsList; -}; - -/** - * Conversion function parameters structure that will be used to call the function. - */ -export interface ConversionFunctionStructure { - functionName: string; - instance: any; - instanceKey?: string; - type?: any; - json: any; - jsonKey?: string; -} - -/** - * Object to cache deserializers - */ -export const 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. - */ -export const getOrCreateDeserializer = (type: any): any => { - return getCachedType(type, deserializers); -}; - -/** - * List of JSON object conversion functions. - */ -export const 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; +import { AccessType, Deserializer, JsonConverstionError , JsonPropertyDecoratorMetadata} from './DecoratorMetadata'; +import { Constants, getCachedType, getJsonPropertyDecoratorMetadata, getTypeName, getTypeNameFromInstance, isArrayType, isSimpleType, METADATA_JSON_IGNORE_NAME, METADATA_JSON_PROPERTIES_NAME } from './ReflectHelper'; + +declare var Reflect; + +const SimpleTypeCoverter = (value: any, type: string): any => { + return type === Constants.DATE_TYPE ? new Date(value) : value; +}; + +/** + * Deserializes a standard js object type(string, number and boolean) from json. + */ +export const DeserializeSimpleType = (instance: Object, instanceKey: string, type: any, json: any, jsonKey: string) => { + 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. + */ +export const DeserializeDateType = (instance: Object, instanceKey: string, type: any, json: any, jsonKey: string): Array => { + 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. + */ +export let DeserializeArrayType = (instance: Object, instanceKey: string, type: any, json: Object, jsonKey: string): Array => { + const jsonObject = (jsonKey !== undefined) ? (json[jsonKey] || []) : json; + const jsonArraySize = jsonObject.length; + const conversionFunctionsList = []; + const arrayInstance = []; + instance[instanceKey] = arrayInstance; + if (jsonArraySize > 0) { + for (let i = 0; i < jsonArraySize; i++) { + if (jsonObject[i]) { + const typeName = getTypeNameFromInstance(type); + if (!isSimpleType(typeName)) { + const 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. + */ +export const DeserializeComplexType = (instance: Object, instanceKey: string, type: any, json: any, jsonKey: string): Array => { + const conversionFunctionsList = []; + + let 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 { + Object.keys(json).forEach((key: string) => instance[key] = undefined); + objectInstance = instance; + } + + let objectKeys: string[] = Object.keys(objectInstance); + objectKeys = objectKeys.concat((Reflect.getMetadata(METADATA_JSON_PROPERTIES_NAME, objectInstance) || []).filter((item: string) => { + 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((item: string) => { + return !Reflect.hasMetadata(METADATA_JSON_IGNORE_NAME, objectInstance, item); + }); + objectKeys.forEach((key: string) => { + /** + * Check if there is any DecoratorMetadata attached to this property, otherwise create a new one. + */ + let metadata: JsonPropertyDecoratorMetadata = 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 + const 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 + const 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 { + const moreFunctions: Array = conversionFunctions[Constants.ARRAY_TYPE](objectInstance, key, metadata.type, json, jsonKeyName); + moreFunctions.forEach((struct: ConversionFunctionStructure) => { + conversionFunctionsList.push(struct); + }); + } + } + } + } + + }); + + return conversionFunctionsList; +}; + +/** + * Conversion function parameters structure that will be used to call the function. + */ +export interface ConversionFunctionStructure { + functionName: string; + instance: any; + instanceKey?: string; + type?: any; + json: any; + jsonKey?: string; +} + +/** + * Object to cache deserializers + */ +export const 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. + */ +export const getOrCreateDeserializer = (type: any): any => { + return getCachedType(type, deserializers); +}; + +/** + * List of JSON object conversion functions. + */ +export const 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; diff --git a/src/main/index.ts b/src/main/index.ts index f856092..6ab0b07 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -23,7 +23,6 @@ export namespace ObjectMapper { */ export const deserialize = (type: { new(): T }, json: Object): T => { const dtoInstance = new type(); - Object.keys(json).forEach((key: string) => dtoInstance[key] = undefined); const conversionFunctionStructure: ConversionFunctionStructure = { functionName: Constants.OBJECT_TYPE, instance: dtoInstance,