Skip to content

Commit

Permalink
Merge pull request #10 from smardev-inc/feature/improve_auto_mapping
Browse files Browse the repository at this point in the history
Issue #8
  • Loading branch information
pedromvgomes committed Aug 19, 2020
2 parents 7c090d6 + 8b6e4e5 commit e46e31b
Show file tree
Hide file tree
Showing 7 changed files with 346 additions and 149 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Websites, mobile applications and others developed using TypeScript more and mor
![License](https://img.shields.io/github/license/smardev-inc/tsmapper)
![Version](https://img.shields.io/github/package-json/v/smardev-inc/tsmapper)
![Build](https://img.shields.io/github/workflow/status/smardev-inc/tsmapper/Continuous%20Integration)
![Statements](https://img.shields.io/badge/Coverage-92.81%25-brightgreen.svg)
![Statements](https://img.shields.io/badge/Coverage-93.14%25-brightgreen.svg)
![Issues](https://img.shields.io/github/issues/smardev-inc/tsmapper)
![Issues](https://img.shields.io/github/commit-activity/w/smardev-inc/tsmapper)

Expand Down
48 changes: 38 additions & 10 deletions src/lib/objectmapper.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/* eslint @typescript-eslint/no-explicit-any: 0 */
/* eslint @typescript-eslint/no-non-null-assertion: 0 */

import * as Utils from './utils';
import { TypeDescriptor } from './typedescriptor';
Expand All @@ -8,9 +9,11 @@ import { ObjectMapConfiguration } from './objectmapconfiguration';
* Provides object mapping methods
*/
export class ObjectMapper {
private static typeDescriptors = new Map<string, TypeDescriptor>();
//private static typeDescriptors = new Map<string, TypeDescriptor>();
private static mappingNamePrefixes = ['', 'm_', '_'];

private static getTypeDescriptor(obj: any): TypeDescriptor {
/*TODO: The caching is not working, need to sort this out
let clsid = obj.constructor.prototype['CLSID'];
if (Utils.isNullOrUndefined(clsid)) {
clsid = '__TSMAPPER_' + ObjectMapper.typeDescriptors.size;
Expand All @@ -20,9 +23,10 @@ export class ObjectMapper {
if (ObjectMapper.typeDescriptors.has(clsid)) {
return <TypeDescriptor>ObjectMapper.typeDescriptors.get(clsid);
}
*/

const descriptor = TypeDescriptor.create(obj);
ObjectMapper.typeDescriptors.set(clsid, descriptor);
//ObjectMapper.typeDescriptors.set(clsid, descriptor);
return descriptor;
}

Expand Down Expand Up @@ -146,6 +150,34 @@ export class ObjectMapper {
return ObjectMapper.autoMapInternal(source, factory);
}

private static getPropertyValue(sourceDescriptor: TypeDescriptor, source: any, name: string, prefixes: string[]): any {
for (let i = 0; i < prefixes.length; i++) {
const fullName = prefixes[i] + name;

// order is: First search for case sensitive property, next case sensitive field, and after case insensitive
// property followed by a case insensitive field
let property = sourceDescriptor.getProperty(fullName, false);
if (!Utils.isUndefined(property)) {
return source[property!.name];
}

let field = sourceDescriptor.getField(fullName, false);
if (!Utils.isUndefined(field)) {
return source[field!.name];
}

property = sourceDescriptor.getProperty(fullName, true);
if (!Utils.isUndefined(property)) {
return source[property!.name];
}

field = sourceDescriptor.getField(fullName, true);
if (!Utils.isUndefined(field)) {
return source[field!.name];
}
}
}

private static autoMapInternal(source: any, factory: () => any): any {
// mapping from source array
if (Array.isArray(source)) {
Expand All @@ -154,16 +186,12 @@ export class ObjectMapper {
return array;
}

const descriptor = ObjectMapper.getTypeDescriptor(source);

const result = factory();
Array.from(descriptor.propertyNames).forEach((name) => {
const value: any = (<any>source)[name];
(<any>result)[name] = value;
});
const sourceDescriptor = ObjectMapper.getTypeDescriptor(source);
const targetDescriptor = ObjectMapper.getTypeDescriptor(result);

Array.from(descriptor.fieldNames).forEach((name) => {
const value: any = (<any>source)[name];
Array.from(targetDescriptor.propertyNames).forEach((name) => {
const value: any = ObjectMapper.getPropertyValue(sourceDescriptor, source, name, ObjectMapper.mappingNamePrefixes);
(<any>result)[name] = value;
});

Expand Down
32 changes: 27 additions & 5 deletions src/lib/typedescriptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { FieldDescriptor } from './fielddescriptor';

export class TypeDescriptor {
private properties: Map<string, PropertyDescriptor> = new Map<string, PropertyDescriptor>();
private fields: Map<string, FieldDescriptor> = new Map<string, PropertyDescriptor>();
private fields: Map<string, FieldDescriptor> = new Map<string, FieldDescriptor>();

private addProperty(value: PropertyDescriptor) {
this.properties.set(value.name, value);
Expand All @@ -16,8 +16,30 @@ export class TypeDescriptor {
return this.properties.has(name);
}

public getProperty(name: string): PropertyDescriptor {
return <PropertyDescriptor>this.properties.get(name);
private static mapSearch<T>(map: Map<string, T>, name: string, caseSensitiveSearch = true): T | undefined {
if (caseSensitiveSearch) {
return map.get(name);
} else {
let count = 0;
let result: T | undefined = undefined;
const nameLower = name.toLowerCase();
map.forEach((value: T, key: string) => {
if (key.toLowerCase() == nameLower) {
count++;
result = value;
}
});

if (count > 1) {
throw new Error('Multiple properties with same name and different case. Case insensitive search not possible');
}

return result;
}
}

public getProperty(name: string, caseSensitiveSearch = true): PropertyDescriptor | undefined {
return TypeDescriptor.mapSearch(this.properties, name, caseSensitiveSearch);
}

public get propertyNames(): IterableIterator<string> {
Expand All @@ -32,8 +54,8 @@ export class TypeDescriptor {
return this.fields.has(name);
}

public getField(name: string): FieldDescriptor {
return <FieldDescriptor>this.fields.get(name);
public getField(name: string, caseSensitiveSearch = true): FieldDescriptor | undefined {
return TypeDescriptor.mapSearch(this.fields, name, caseSensitiveSearch);
}

public get fieldNames(): IterableIterator<string> {
Expand Down
152 changes: 152 additions & 0 deletions src/tests/data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
export const usersData = [
{
id: 1,
profilePicture: 'https://robohash.org/sitdoloremquecumque.png?size=50x50&set=set1',
userName: 'vmcharg0',
first_name: 'Virgina',
last_name: 'Mc Harg',
gender: 'Female',
email: 'vmcharg0@cam.ac.uk',
phone: '602-877-7674',
birthDate: '1992-03-14T14:40:14Z',
country: 'Sweden'
},
{
id: 2,
profilePicture: null,
userName: 'pharcombe1',
first_name: 'Philly',
last_name: 'Harcombe',
gender: 'Female',
email: 'pharcombe1@usa.gov',
phone: '869-788-3634',
birthDate: '1986-04-21T23:03:36Z',
country: 'China'
},
{
id: 3,
profilePicture: null,
userName: 'ccotte2',
first_name: 'Casey',
last_name: 'Cotte',
gender: 'Male',
email: 'ccotte2@domainmarket.com',
phone: '388-256-4344',
birthDate: '1964-06-09T18:14:04Z',
country: 'Indonesia'
},
{
id: 4,
profilePicture: 'https://robohash.org/atquepraesentiumut.png?size=50x50&set=set1',
userName: 'mkemitt3',
first_name: 'Mahmoud',
last_name: 'Kemitt',
gender: 'Male',
email: 'mkemitt3@hc360.com',
phone: '167-423-6408',
birthDate: '1965-10-06T06:26:55Z',
country: 'Croatia'
}
];

export const usersDataPrivateFields = [
{
m_id: 1,
m_profilePicture: 'https://robohash.org/sitdoloremquecumque.png?size=50x50&set=set1',
m_userName: 'vmcharg0',
m_first_name: 'Virgina',
m_last_name: 'Mc Harg',
m_gender: 'Female',
m_email: 'vmcharg0@cam.ac.uk',
m_phone: '602-877-7674',
m_birthDate: '1992-03-14T14:40:14Z',
m_country: 'Sweden'
},
{
m_id: 2,
m_profilePicture: null,
m_userName: 'pharcombe1',
m_first_name: 'Philly',
m_last_name: 'Harcombe',
m_gender: 'Female',
m_email: 'pharcombe1@usa.gov',
m_phone: '869-788-3634',
m_birthDate: '1986-04-21T23:03:36Z',
m_country: 'China'
},
{
m_id: 3,
m_profilePicture: null,
m_userName: 'ccotte2',
m_first_name: 'Casey',
m_last_name: 'Cotte',
m_gender: 'Male',
m_email: 'ccotte2@domainmarket.com',
m_phone: '388-256-4344',
m_birthDate: '1964-06-09T18:14:04Z',
m_country: 'Indonesia'
},
{
m_id: 4,
m_profilePicture: 'https://robohash.org/atquepraesentiumut.png?size=50x50&set=set1',
m_userName: 'mkemitt3',
m_first_name: 'Mahmoud',
m_last_name: 'Kemitt',
m_gender: 'Male',
m_email: 'mkemitt3@hc360.com',
m_phone: '167-423-6408',
m_birthDate: '1965-10-06T06:26:55Z',
m_country: 'Croatia'
}
];

export const usersDataUperCase = [
{
ID: 1,
PROFILEPICTURE: 'https://robohash.org/sitdoloremquecumque.png?size=50x50&set=set1',
USERNAME: 'vmcharg0',
FIRST_NAME: 'Virgina',
LAST_NAME: 'Mc Harg',
GENDER: 'Female',
EMAIL: 'vmcharg0@cam.ac.uk',
PHONE: '602-877-7674',
BIRTHDATE: '1992-03-14T14:40:14Z',
COUNTRY: 'Sweden'
},
{
ID: 2,
PROFILEPICTURE: null,
USERNAME: 'pharcombe1',
FIRST_NAME: 'Philly',
LAST_NAME: 'Harcombe',
GENDER: 'Female',
EMAIL: 'pharcombe1@usa.gov',
PHONE: '869-788-3634',
BIRTHDATE: '1986-04-21T23:03:36Z',
COUNTRY: 'China'
},
{
ID: 3,
PROFILEPICTURE: null,
USERNAME: 'ccotte2',
FIRST_NAME: 'Casey',
LAST_NAME: 'Cotte',
GENDER: 'Male',
EMAIL: 'ccotte2@domainmarket.com',
PHONE: '388-256-4344',
BIRTHDATE: '1964-06-09T18:14:04Z',
COUNTRY: 'Indonesia'
},
{
ID: 4,
PROFILEPICTURE: 'https://robohash.org/atquepraesentiumut.png?size=50x50&set=set1',
USERNAME: 'mkemitt3',
FIRST_NAME: 'Mahmoud',
LAST_NAME: 'Kemitt',
GENDER: 'Male',
EMAIL: 'mkemitt3@hc360.com',
PHONE: '167-423-6408',
BIRTHDATE: '1965-10-06T06:26:55Z',
COUNTRY: 'Croatia'
}
];
65 changes: 65 additions & 0 deletions src/tests/models.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
export class User {
private _id: string | undefined;
private _profilePicture: string | undefined;
private _userName: string | undefined;
private _displayName: string | undefined;
private _email: string | undefined;
private _birthDate: Date | undefined;
private _phone: string | undefined;

public get id(): string | undefined {
return this._id;
}

public set id(value: string | undefined) {
this._id = value;
}

public get profilePicture(): string | undefined {
return this._profilePicture;
}

public set profilePicture(value: string | undefined) {
this._profilePicture = value;
}

public get userName(): string | undefined {
return this._userName;
}

public set userName(value: string | undefined) {
this._userName = value;
}

public get displayName(): string | undefined {
return this._displayName;
}

public set displayName(value: string | undefined) {
this._displayName = value;
}

public get email(): string | undefined {
return this._email;
}

public set email(value: string | undefined) {
this._email = value;
}

public get birthDate(): Date | undefined {
return this._birthDate;
}

public set birthDate(value: Date | undefined) {
this._birthDate = value;
}

public get phone(): string | undefined {
return this._phone;
}

public set phone(value: string | undefined) {
this._phone = value;
}
}
Loading

0 comments on commit e46e31b

Please sign in to comment.