mapper-factory 4.0.0 → 4.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,215 +1,215 @@
1
- # Mapper-Factory
2
-
3
- Mapper-Factory is a fully documented TypeScript library that provides a simple and easy-to-use way to map objects from one type to another. With just a few lines of code, you can convert complex, nested objects into the desired format.
4
-
5
- Work well on data structure and after enjoy the coding process :)
6
-
7
- ## Installation
8
-
9
- To install the package, you can use npm by running the following command:
10
-
11
- ```
12
- npm i mapper-factory
13
- ```
14
-
15
- Or using yarn
16
-
17
- ```
18
- yarn add mapper-factory
19
- ```
20
-
21
- ## Problem to solve
22
-
23
- We want to solve the problem to trasform a JSON object to a specific JS object adding custom properties, with integrated mapping. As example this JSON object:
24
-
25
- ```
26
- {
27
- firstName: 'Rick',
28
- lastName: 'Sanchez',
29
- employees: [
30
- { firstName: 'Summer', lastName: 'Smith' },
31
- { firstName: 'Morty', lastName: 'Smith' }
32
- ],
33
- rolesToMap: [ 'CEO', 'EMPLOYEE' ],
34
- boss: { firstName: 'Jesus', lastName: 'Christ' }
35
- }
36
- ```
37
-
38
- must became a JS object:
39
-
40
- ```
41
- User {
42
- name: 'Rick',
43
- surname: 'Sanchez',
44
- employees: [
45
- User { name: 'Summer', surname: 'Smith' },
46
- User { name: 'Morty', surname: 'Smith' }
47
- ],
48
- roles: [ 'CEO TEST TRASFORMER', 'EMPLOYEE TEST TRASFORMER' ],
49
- boss: User { name: 'Jesus', surname: 'Christ' }
50
- /** maybe some methods... */
51
- }
52
- ```
53
-
54
- ## Usage V2
55
-
56
- ### Mapping simple objects
57
-
58
- Your class must use _MapClass_ decorator and set an interface that extends _MapInterface<T>_
59
- In this way your class could extend another, but continue using intellisense methods for YourClass
60
-
61
- ```
62
- @MapClass()
63
- class YourClass extends YourCustomClassToExtend {
64
- /** YOUR PROPERTIES */
65
- }
66
- interface YourClass extends MapInterface<YourClass> { }
67
- ```
68
-
69
- To get new _YourClass_ instance it's simple as always:
70
-
71
- ```
72
- new YourClass();
73
- ```
74
-
75
- but now you can easly mapping an object just using _MapInterface_ method _from_:
76
-
77
- ```
78
- const yourInstance: YourClass = new YourClass().from(/** YOUR OBJECT TO MAP HERE*/);
79
- ```
80
-
81
- and reverse your mapping using using _MapInterface_ method _toMap_:
82
-
83
- ```
84
- const reversedMapping: Object = yourInstance.toMap();
85
- ```
86
-
87
- With _MapInterface_ you can use on your class instance also other methods:
88
-
89
- - **_from_**: Create a new instance using model to map
90
-
91
- ```
92
- const yourInstance: YourClass = new YourClass().from(/** YOUR OBJECT TO MAP HERE */);
93
- ```
94
-
95
- - **_toMap_**: Create a JSON object using reverse mapping
96
-
97
- ```
98
- const reverseMappedObject: Object = yourInstance.toMap();
99
- ```
100
-
101
- - **_toModel_**: Create a new instance using same model of final JS object
102
-
103
- ```
104
- const anotherInstance: YourClass = new YourClass.toModel(yourInstance);
105
- ```
106
-
107
- - **_empty_**: Check if the object is empty
108
-
109
- ```
110
- const isEmpty: boolean = yourInstance.empty();
111
- ```
112
-
113
- - **_filled_**: Check if the object is filled
114
-
115
- ```
116
- const isFilled: boolean = yourInstance.filled();
117
- ```
118
-
119
- - **_get_**: Get specific property of the object
120
-
121
- ```
122
- const specificProp: T = yourInstance.get('specificProp') as T;
123
- ```
124
-
125
- - **_set_**: Set specific property of the object
126
-
127
- ```
128
- yourInstance.set('specificProp', /** NEW VALUE FOR 'specificProp' */);
129
- ```
130
-
131
- - **_copy_**: Deep copy of the object
132
-
133
- ```
134
- const yourInstanceCopy: YourClass = yourInstance.copy();
135
- ```
136
-
137
- After that, you can use _@MapField_ decorator over single property to specify the mapping. Let's dive into an example:
138
-
139
- ```
140
- @MapClass()
141
- class User {
142
-
143
- @MapField({
144
- src: 'firstName'
145
- })
146
- name: string;
147
-
148
- @MapField({
149
- src: 'obj.obj[0][1]',
150
- transformer: (arr) => arr.map(role => role + " TEST TRASFORMER"),
151
- reverser: (arr) => arr.map(role => role.replace(" TEST TRASFORMER", "")),
152
- })
153
- roles?: string[];
154
-
155
- @MapField({
156
- transformer: (user) => new User(user)
157
- })
158
- boss: User;
159
- }
160
- interface User extends MapInterface<User> { }
161
- ```
162
-
163
- Inside _@MapField_ you can use:
164
-
165
- - **_src_**: define a string of original field name (also using a path like _"obj.obj[0][1]"_)
166
- - **_transform_**: function to transform data input in _constructor_ of the class
167
- - **_reverse_**: function to transform data input in _toMap_ method of the class
168
-
169
- In this example:
170
-
171
- ```
172
- @MapClass()
173
- class User {
174
-
175
- id: string;
176
- username: string;
177
-
178
- @MapField({
179
- src: 'firstName'
180
- })
181
- name: string;
182
-
183
- @MapField({
184
- src: 'lastName'
185
- })
186
- surname: string;
187
-
188
- @MapField({
189
- src: 'rolesToMap',
190
- transformer: (arr) => arr.map(role => role + " TEST TRASFORMER"),
191
- reverser: (arr) => arr.map(role => role.replace(" TEST TRASFORMER", "")),
192
- })
193
- roles?: string[];
194
-
195
- @MapField({
196
- transformer: (arr) => arr.map(user => new User(user))
197
- })
198
- employees?: User[];
199
-
200
- @MapField({
201
- transformer: (user) => new User(user)
202
- })
203
- boss?: User;
204
- }
205
- interface User extends MapInterface<User> { }
206
- ```
207
-
208
- You can define a new User **_u_**, using two employees (using the same User model here but for a different object is the same):
209
-
210
- ```
211
- let emp1: User = new User().from({ firstName: "Summer", lastName: "Smith" });
212
- let emp2: User = new User().from({ firstName: "Morty", lastName: "Smith" });
213
-
214
- let u = new User().from({ firstName: "Rick", lastName: "Sanchez", employees: [emp1.toMap(), emp2.toMap()], rolesToMap: ["CEO", "EMPLOYEE"] });
215
- ```
1
+ # Mapper-Factory
2
+
3
+ Mapper-Factory is a fully documented TypeScript library that provides a simple and easy-to-use way to map objects from one type to another. With just a few lines of code, you can convert complex, nested objects into the desired format.
4
+
5
+ Work well on data structure and after enjoy the coding process :)
6
+
7
+ ## Installation
8
+
9
+ To install the package, you can use npm by running the following command:
10
+
11
+ ```
12
+ npm i mapper-factory
13
+ ```
14
+
15
+ Or using yarn
16
+
17
+ ```
18
+ yarn add mapper-factory
19
+ ```
20
+
21
+ ## Problem to solve
22
+
23
+ We want to solve the problem to trasform a JSON object to a specific JS object adding custom properties, with integrated mapping. As example this JSON object:
24
+
25
+ ```
26
+ {
27
+ firstName: 'Rick',
28
+ lastName: 'Sanchez',
29
+ employees: [
30
+ { firstName: 'Summer', lastName: 'Smith' },
31
+ { firstName: 'Morty', lastName: 'Smith' }
32
+ ],
33
+ rolesToMap: [ 'CEO', 'EMPLOYEE' ],
34
+ boss: { firstName: 'Jesus', lastName: 'Christ' }
35
+ }
36
+ ```
37
+
38
+ must became a JS object:
39
+
40
+ ```
41
+ User {
42
+ name: 'Rick',
43
+ surname: 'Sanchez',
44
+ employees: [
45
+ User { name: 'Summer', surname: 'Smith' },
46
+ User { name: 'Morty', surname: 'Smith' }
47
+ ],
48
+ roles: [ 'CEO TEST TRASFORMER', 'EMPLOYEE TEST TRASFORMER' ],
49
+ boss: User { name: 'Jesus', surname: 'Christ' }
50
+ /** maybe some methods... */
51
+ }
52
+ ```
53
+
54
+ ## Usage V2
55
+
56
+ ### Mapping simple objects
57
+
58
+ Your class must use _MapClass_ decorator and set an interface that extends _MapInterface<T>_
59
+ In this way your class could extend another, but continue using intellisense methods for YourClass
60
+
61
+ ```
62
+ @MapClass()
63
+ class YourClass extends YourCustomClassToExtend {
64
+ /** YOUR PROPERTIES */
65
+ }
66
+ interface YourClass extends MapInterface<YourClass> { }
67
+ ```
68
+
69
+ To get new _YourClass_ instance it's simple as always:
70
+
71
+ ```
72
+ new YourClass();
73
+ ```
74
+
75
+ but now you can easly mapping an object just using _MapInterface_ method _from_:
76
+
77
+ ```
78
+ const yourInstance: YourClass = new YourClass().from(/** YOUR OBJECT TO MAP HERE*/);
79
+ ```
80
+
81
+ and reverse your mapping using using _MapInterface_ method _toMap_:
82
+
83
+ ```
84
+ const reversedMapping: Object = yourInstance.toMap();
85
+ ```
86
+
87
+ With _MapInterface_ you can use on your class instance also other methods:
88
+
89
+ - **_from_**: Create a new instance using model to map
90
+
91
+ ```
92
+ const yourInstance: YourClass = new YourClass().from(/** YOUR OBJECT TO MAP HERE */);
93
+ ```
94
+
95
+ - **_toMap_**: Create a JSON object using reverse mapping
96
+
97
+ ```
98
+ const reverseMappedObject: Object = yourInstance.toMap();
99
+ ```
100
+
101
+ - **_toModel_**: Create a new instance using same model of final JS object
102
+
103
+ ```
104
+ const anotherInstance: YourClass = new YourClass.toModel(yourInstance);
105
+ ```
106
+
107
+ - **_empty_**: Check if the object is empty
108
+
109
+ ```
110
+ const isEmpty: boolean = yourInstance.empty();
111
+ ```
112
+
113
+ - **_filled_**: Check if the object is filled
114
+
115
+ ```
116
+ const isFilled: boolean = yourInstance.filled();
117
+ ```
118
+
119
+ - **_get_**: Get specific property of the object
120
+
121
+ ```
122
+ const specificProp: T = yourInstance.get('specificProp') as T;
123
+ ```
124
+
125
+ - **_set_**: Set specific property of the object
126
+
127
+ ```
128
+ yourInstance.set('specificProp', /** NEW VALUE FOR 'specificProp' */);
129
+ ```
130
+
131
+ - **_copy_**: Deep copy of the object
132
+
133
+ ```
134
+ const yourInstanceCopy: YourClass = yourInstance.copy();
135
+ ```
136
+
137
+ After that, you can use _@MapField_ decorator over single property to specify the mapping. Let's dive into an example:
138
+
139
+ ```
140
+ @MapClass()
141
+ class User {
142
+
143
+ @MapField({
144
+ src: 'firstName'
145
+ })
146
+ name: string;
147
+
148
+ @MapField({
149
+ src: 'obj.obj[0][1]',
150
+ transformer: (arr) => arr.map(role => role + " TEST TRASFORMER"),
151
+ reverser: (arr) => arr.map(role => role.replace(" TEST TRASFORMER", "")),
152
+ })
153
+ roles?: string[];
154
+
155
+ @MapField({
156
+ transformer: (user) => new User(user)
157
+ })
158
+ boss: User;
159
+ }
160
+ interface User extends MapInterface<User> { }
161
+ ```
162
+
163
+ Inside _@MapField_ you can use:
164
+
165
+ - **_src_**: define a string of original field name (also using a path like _"obj.obj[0][1]"_)
166
+ - **_transform_**: function to transform data input in _constructor_ of the class
167
+ - **_reverse_**: function to transform data input in _toMap_ method of the class
168
+
169
+ In this example:
170
+
171
+ ```
172
+ @MapClass()
173
+ class User {
174
+
175
+ id: string;
176
+ username: string;
177
+
178
+ @MapField({
179
+ src: 'firstName'
180
+ })
181
+ name: string;
182
+
183
+ @MapField({
184
+ src: 'lastName'
185
+ })
186
+ surname: string;
187
+
188
+ @MapField({
189
+ src: 'rolesToMap',
190
+ transformer: (arr) => arr.map(role => role + " TEST TRASFORMER"),
191
+ reverser: (arr) => arr.map(role => role.replace(" TEST TRASFORMER", "")),
192
+ })
193
+ roles?: string[];
194
+
195
+ @MapField({
196
+ transformer: (arr) => arr.map(user => new User(user))
197
+ })
198
+ employees?: User[];
199
+
200
+ @MapField({
201
+ transformer: (user) => new User(user)
202
+ })
203
+ boss?: User;
204
+ }
205
+ interface User extends MapInterface<User> { }
206
+ ```
207
+
208
+ You can define a new User **_u_**, using two employees (using the same User model here but for a different object is the same):
209
+
210
+ ```
211
+ let emp1: User = new User().from({ firstName: "Summer", lastName: "Smith" });
212
+ let emp2: User = new User().from({ firstName: "Morty", lastName: "Smith" });
213
+
214
+ let u = new User().from({ firstName: "Rick", lastName: "Sanchez", employees: [emp1.toMap(), emp2.toMap()], rolesToMap: ["CEO", "EMPLOYEE"] });
215
+ ```
@@ -1,4 +1,4 @@
1
- import { copy, empty, filled, from, get, objToModel, set, toMap } from "./functions";
1
+ import { copy, empty, filled, from, get, objToModel, set, toMap, } from "./functions";
2
2
  export function MapClass() {
3
3
  return function (constructor) {
4
4
  constructor.prototype.from = from;
@@ -1,4 +1,4 @@
1
1
  import { MapInterface } from "../class.decorator";
2
2
  export declare function ArrayField<T extends MapInterface<T>>(clsFactory: new () => T, opt?: {
3
3
  src?: string;
4
- }): PropertyDecorator;
4
+ }): (target: unknown, propertyKey: string | symbol) => void;
@@ -3,9 +3,7 @@ export function ArrayField(clsFactory, opt) {
3
3
  const Ctor = clsFactory;
4
4
  return MapField({
5
5
  src: opt?.src,
6
- transformer: (arr) => Array.isArray(arr)
7
- ? arr.map((item) => new Ctor().from(item))
8
- : [],
6
+ transformer: (arr) => Array.isArray(arr) ? arr.map((item) => new Ctor().from(item)) : [],
9
7
  reverser: (arr) => Array.isArray(arr) ? arr.map((item) => item.toMap()) : [],
10
8
  });
11
9
  }
@@ -1,3 +1,3 @@
1
1
  export declare function DateField(opt?: {
2
2
  src?: string;
3
- }): PropertyDecorator;
3
+ }): (target: unknown, propertyKey: string | symbol) => void;
@@ -1,5 +1,4 @@
1
- import 'reflect-metadata';
2
- import { ClassType } from '../types';
1
+ import { ClassType } from "../types";
3
2
  export declare const MAP_FIELD: unique symbol;
4
3
  export interface MapperMetadata<T = any> {
5
4
  src?: keyof T;
@@ -13,7 +12,7 @@ export interface MapperMetadata<T = any> {
13
12
  }
14
13
  export declare function isClass(func: any): func is ClassType;
15
14
  export declare function getPrototype(target: Record<string, unknown> | ClassType): any;
16
- export declare const MapField: <T = any>({ transformer, reverser, src, initialize, }?: MapperMetadata<T>) => PropertyDecorator;
15
+ export declare const MapField: <T = any>({ transformer, reverser, src, initialize, }?: MapperMetadata<T>) => ((target: unknown, propertyKey: string | symbol) => void);
17
16
  export declare const getMapFieldMetadataList: (target: Record<string, unknown> | ClassType | any) => {
18
17
  [key: string]: MapperMetadata;
19
18
  } | undefined;
@@ -1,17 +1,20 @@
1
- import 'reflect-metadata';
2
- export const MAP_FIELD = Symbol('MAP_FIELD');
1
+ export const MAP_FIELD = Symbol("MAP_FIELD");
3
2
  export function isClass(func) {
4
- return (typeof func === 'function' &&
3
+ return (typeof func === "function" &&
5
4
  /^class\s/.test(Function.prototype.toString.call(func)));
6
5
  }
7
6
  export function getPrototype(target) {
8
- return isClass(target) || !target?.prototype ? !target?.constructor ? target : target?.constructor : target?.prototype;
7
+ return isClass(target) || !target?.prototype
8
+ ? !target?.constructor
9
+ ? target
10
+ : target?.constructor
11
+ : target?.prototype;
9
12
  }
10
13
  export const MapField = ({ transformer, reverser, src, initialize = false, } = {}) => {
11
14
  return (target, property) => {
12
15
  const classConstructor = target.constructor;
13
16
  const propertyName = property.toString();
14
- const metadata = Reflect.getMetadata(MAP_FIELD, classConstructor) || {};
17
+ const metadata = classConstructor[MAP_FIELD] || {};
15
18
  // create new object reference to avoid this issue: https://github.com/rbuckton/reflect-metadata/issues/62
16
19
  const newMetadata = { ...metadata };
17
20
  const previousValues = metadata[propertyName];
@@ -22,14 +25,14 @@ export const MapField = ({ transformer, reverser, src, initialize = false, } = {
22
25
  transformer,
23
26
  reverser,
24
27
  };
25
- Reflect.defineMetadata(MAP_FIELD, newMetadata, classConstructor);
28
+ classConstructor[MAP_FIELD] = newMetadata;
26
29
  };
27
30
  };
28
31
  export const getMapFieldMetadataList = (target) => {
29
- return Reflect.getMetadata(MAP_FIELD, getPrototype(target));
32
+ return getPrototype(target)[MAP_FIELD];
30
33
  };
31
34
  export const hasMapFieldMetadataList = (target) => {
32
- return Reflect.hasMetadata(MAP_FIELD, getPrototype(target));
35
+ return !!getPrototype(target)[MAP_FIELD];
33
36
  };
34
37
  export const getMapFieldMetadata = (target, propertyName) => {
35
38
  const metadata = getMapFieldMetadataList(target);
@@ -39,6 +42,6 @@ export const getMapFieldMetadata = (target, propertyName) => {
39
42
  return metadata[name];
40
43
  };
41
44
  export const hasMapFieldMetadata = (target, propertyName) => {
42
- const metadata = Reflect.getMetadata(MAP_FIELD, getPrototype(target));
45
+ const metadata = getPrototype(target)[MAP_FIELD];
43
46
  return metadata && !!metadata[propertyName];
44
47
  };
@@ -1,4 +1,4 @@
1
1
  import { MapInterface } from "../class.decorator";
2
2
  export declare function ObjectField<T extends MapInterface<T>>(clsFactory: new () => T, opt?: {
3
3
  src?: string;
4
- }): PropertyDecorator;
4
+ }): (target: unknown, propertyKey: string | symbol) => void;
@@ -1,49 +1,54 @@
1
+ export interface MapperTarget {
2
+ [key: string]: any;
3
+ from?(obj: unknown): MapperTarget;
4
+ toMap?(): Record<string, unknown>;
5
+ }
1
6
  /**
2
7
  * Convert the instance of this class to JSON Object.
3
8
  *
4
9
  * @returns JSON object mapped considering metadata "src" and "reverser"
5
10
  */
6
- export declare function toMap(): any;
11
+ export declare function toMap(this: MapperTarget): Record<string, unknown>;
7
12
  /**
8
13
  * Convert a JSON Object to an instance of this class.
9
14
  *
10
15
  * @param obj JSON Object
11
16
  * @returns Instance of this class
12
17
  */
13
- export declare function objToModel(obj: Object): any;
18
+ export declare function objToModel(this: MapperTarget, obj: Record<string, any>): MapperTarget;
14
19
  /**
15
20
  * Check if this instance is empty.
16
21
  *
17
22
  * @returns true or false
18
23
  */
19
- export declare function empty(): boolean;
24
+ export declare function empty(this: MapperTarget): boolean;
20
25
  /**
21
26
  * Check if this instance is filled.
22
27
  *
23
28
  * @returns true or false
24
29
  */
25
- export declare function filled(): boolean;
30
+ export declare function filled(this: MapperTarget): boolean;
26
31
  /**
27
32
  * GET property value from a string path.
28
33
  *
29
34
  * @param path String path
30
35
  * @returns Value of the property
31
36
  */
32
- export declare function get<T>(path: string): T;
37
+ export declare function get<T>(this: MapperTarget, path: string): T;
33
38
  /**
34
39
  * SET property value from a string path.
35
40
  *
36
41
  * @param path String path
37
42
  * @param value Value of the property
38
43
  */
39
- export declare function set(path: string, value: any): void;
44
+ export declare function set(this: MapperTarget, path: string, value: unknown): void;
40
45
  /**
41
46
  * Deep copy of the object caller
42
47
  */
43
- export declare function copy<T>(): T;
48
+ export declare function copy<T extends MapperTarget>(this: T): T;
44
49
  /**
45
50
  * Constructor of the mapper.
46
51
  *
47
52
  * @param object object to be mapped considering metadata "src", "transformer" and "reverser"
48
53
  */
49
- export declare function from(object: any): any;
54
+ export declare function from(this: MapperTarget, object: Record<string, any>): MapperTarget;
package/dist/functions.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { getMapFieldMetadataList } from "./field-decorators/field.decorator";
2
+ import { getValueByPath, setValueByPath } from "./utils";
2
3
  /**
3
4
  * Convert the instance of this class to JSON Object.
4
5
  *
@@ -6,68 +7,43 @@ import { getMapFieldMetadataList } from "./field-decorators/field.decorator";
6
7
  */
7
8
  export function toMap() {
8
9
  const metadataList = getMapFieldMetadataList(this);
9
- let obj = {};
10
- const processProperty = (objCopy, propsStereoid, value, reverser) => {
11
- let lastIndex;
12
- for (let i = 0; i < propsStereoid.length; i++) {
13
- const prop = propsStereoid[i];
14
- if (prop.isArray) {
15
- let arrIndex = prop.arrIndex
16
- .split(/\[(\w+)\]/g)
17
- .filter((index) => index !== "");
18
- objCopy[prop.prop] = objCopy[prop.prop] || [];
19
- objCopy = objCopy[prop.prop];
20
- arrIndex.forEach((index, i) => {
21
- objCopy[index] =
22
- objCopy[index] || (i == arrIndex.length - 1 ? {} : []);
23
- if (i != arrIndex.length - 1)
24
- objCopy = objCopy[index];
25
- else
26
- lastIndex = index;
27
- });
28
- }
29
- else {
30
- objCopy[prop.prop] = objCopy[prop.prop] || {};
31
- if (i != propsStereoid.length - 1)
32
- objCopy = objCopy[prop.prop];
33
- else
34
- lastIndex = prop.prop;
35
- }
36
- }
37
- objCopy[lastIndex] = reverser ? reverser(value, this) : value;
38
- };
39
- this &&
10
+ const obj = {};
11
+ if (this) {
40
12
  Object.keys(this).forEach((propertyName) => {
41
13
  const metadata = metadataList && metadataList[propertyName];
42
14
  const src = metadata?.src || propertyName;
15
+ const value = this[propertyName];
16
+ let finalValue;
43
17
  if (metadata) {
44
- if (src.includes(".")) {
45
- let props = src.split(".");
46
- let propsStereoid = props.map((prop) => ({
47
- prop: prop.includes("[")
48
- ? prop.substring(0, prop.indexOf("["))
49
- : prop,
50
- isArray: prop.includes("[") && prop.includes("]"),
51
- arrIndex: prop.substring(prop.indexOf("[")),
52
- }));
53
- processProperty(obj, propsStereoid, this[propertyName], metadata.reverser);
18
+ if (Array.isArray(value) && !metadata.reverser) {
19
+ finalValue = value.map((item) => (item?.toMap ? item.toMap() : item));
20
+ }
21
+ else if (metadata.reverser) {
22
+ finalValue = metadata.reverser(value, this);
23
+ }
24
+ else if (value?.toMap) {
25
+ finalValue = value.toMap();
54
26
  }
55
27
  else {
56
- obj[src] =
57
- Array.isArray(this[propertyName]) && !metadata.reverser
58
- ? this[propertyName].map((item) => item?.toMap ? item.toMap() : item)
59
- : metadata.reverser
60
- ? metadata.reverser(this[propertyName], this)
61
- : this[propertyName]?.toMap
62
- ? this[propertyName].toMap()
63
- : this[propertyName];
28
+ finalValue = value;
64
29
  }
65
30
  }
66
31
  else {
67
- if (this[propertyName] != undefined)
68
- obj[propertyName] = this[propertyName];
32
+ if (Array.isArray(value)) {
33
+ finalValue = value.map((item) => (item?.toMap ? item.toMap() : item));
34
+ }
35
+ else if (value?.toMap) {
36
+ finalValue = value.toMap();
37
+ }
38
+ else {
39
+ finalValue = value;
40
+ }
41
+ }
42
+ if (finalValue !== undefined) {
43
+ setValueByPath(obj, src, finalValue);
69
44
  }
70
45
  });
46
+ }
71
47
  return obj;
72
48
  }
73
49
  /**
@@ -111,8 +87,7 @@ export function filled() {
111
87
  * @returns Value of the property
112
88
  */
113
89
  export function get(path) {
114
- const props = path.replace(/\[(\w+)\]/g, ".$1").split(".");
115
- return props.reduce((acc, prop) => acc && acc[prop], this);
90
+ return getValueByPath(this, path);
116
91
  }
117
92
  /**
118
93
  * SET property value from a string path.
@@ -121,20 +96,16 @@ export function get(path) {
121
96
  * @param value Value of the property
122
97
  */
123
98
  export function set(path, value) {
124
- const props = path.replace(/\[(\w+)\]/g, ".$1").split(".");
125
- let obj = this;
126
- props.slice(0, -1).forEach((prop) => {
127
- if (!obj[prop])
128
- obj[prop] = {};
129
- obj = obj[prop];
130
- });
131
- obj[props[props.length - 1]] = value;
99
+ setValueByPath(this, path, value);
132
100
  }
133
101
  /**
134
102
  * Deep copy of the object caller
135
103
  */
136
104
  export function copy() {
137
- return this.from(this.toMap());
105
+ if (this.from && this.toMap) {
106
+ return this.from(this.toMap());
107
+ }
108
+ return this;
138
109
  }
139
110
  /**
140
111
  * Constructor of the mapper.
@@ -143,77 +114,50 @@ export function copy() {
143
114
  */
144
115
  export function from(object) {
145
116
  const metadataList = getMapFieldMetadataList(this);
146
- const processProperty = (objCopy, propsStereoid) => {
147
- for (let i = 0; i < propsStereoid.length; i++) {
148
- const prop = propsStereoid[i];
149
- if (prop.isArray) {
150
- let arrIndex = prop.arrIndex
151
- .split(/\[(\w+)\]/g)
152
- .filter((index) => index !== "");
153
- objCopy = objCopy[prop.prop];
154
- arrIndex.forEach((index) => {
155
- objCopy = objCopy[index];
156
- });
157
- }
158
- else {
159
- objCopy = objCopy[prop.prop];
160
- }
161
- }
162
- return objCopy;
163
- };
164
- const setProperty = (metaKey, value, object) => {
165
- const metaProp = metadataList?.[metaKey];
166
- if (metaProp?.transformer) {
167
- const valueTransformed = metaProp.transformer(value, object);
168
- if (valueTransformed != undefined)
169
- this[metaKey] = valueTransformed;
170
- }
171
- else {
172
- if (value != undefined)
173
- this[metaKey] = value;
174
- }
175
- };
176
- object &&
177
- Object.keys(object).forEach((propertyName) => {
178
- let metaKeys = metadataList &&
179
- Object.keys(metadataList).filter((metadata) => metadataList[metadata]?.src?.split(".")?.includes(propertyName));
180
- if (metaKeys?.length) {
181
- metaKeys.forEach((metaKey) => {
182
- const metaProp = metadataList?.[metaKey];
183
- if (metaProp) {
184
- const props = metaProp.src.split(".");
185
- const propsStereoid = props.map((prop) => ({
186
- prop: prop.includes("[")
187
- ? prop.substring(0, prop.indexOf("["))
188
- : prop,
189
- isArray: prop.includes("[") && prop.includes("]"),
190
- arrIndex: prop.substring(prop.indexOf("[")),
191
- }));
192
- const value = processProperty({ ...object }, propsStereoid);
193
- setProperty(metaKey, value, object);
117
+ const mappedSrcRoots = new Set();
118
+ // 1. Process Metadata
119
+ if (metadataList && object) {
120
+ Object.keys(metadataList).forEach((key) => {
121
+ const meta = metadataList[key];
122
+ const src = meta.src || key;
123
+ // Identify root property for this mapping to exclude it from direct copy later
124
+ const normalizedPath = src.replace(/\[(\w+)\]/g, ".$1");
125
+ const root = normalizedPath.split(".")[0];
126
+ mappedSrcRoots.add(root);
127
+ const value = getValueByPath(object, src);
128
+ // Only process if value exists in source (matches old behavior and avoids infinite recursion on undefined)
129
+ if (value !== undefined) {
130
+ if (meta.transformer) {
131
+ const transformed = meta.transformer(value, object);
132
+ if (transformed !== undefined) {
133
+ this[key] = transformed;
194
134
  }
195
- });
196
- }
197
- else {
198
- let metaKey = metadataList &&
199
- Object.keys(metadataList).find((metadata) => metadataList[metadata]?.src == propertyName);
200
- if (metaKey) {
201
- const src = metadataList?.[metaKey].src || propertyName;
202
- setProperty(metaKey, object[src], object);
203
135
  }
204
136
  else {
205
- setProperty(propertyName, object[propertyName], object);
137
+ this[key] = value;
206
138
  }
207
139
  }
208
140
  });
209
- // Initialize properties with "initialize" metadata
210
- metadataList &&
141
+ }
142
+ // 2. Process Remaining Properties (Direct Copy)
143
+ // Only if they are not part of a mapped source root
144
+ if (object) {
145
+ Object.keys(object).forEach((prop) => {
146
+ if (!mappedSrcRoots.has(prop)) {
147
+ this[prop] = object[prop];
148
+ }
149
+ });
150
+ }
151
+ // 3. Initialize properties with "initialize" metadata
152
+ if (metadataList) {
211
153
  Object.keys(metadataList).forEach((metaName) => {
212
- if (metadataList[metaName]?.initialize &&
213
- metadataList[metaName]?.transformer &&
154
+ const meta = metadataList[metaName];
155
+ if (meta?.initialize &&
156
+ meta?.transformer &&
214
157
  this[metaName] === undefined) {
215
- this[metaName] = metadataList[metaName].transformer(null, object);
158
+ this[metaName] = meta.transformer(null, object);
216
159
  }
217
160
  });
161
+ }
218
162
  return this;
219
163
  }
package/dist/index.d.ts CHANGED
@@ -7,7 +7,7 @@ import { ClassType } from "./types";
7
7
  export { ClassType, MapInterface, MapClass, MapField, DateField, ArrayField, ObjectField, };
8
8
  /**
9
9
  * npx tsc
10
- * npx ts-node src/test.ts
10
+ * npx tsx src/test.ts
11
11
  * npm version ( patch | minor | major )
12
12
  * npm publish
13
13
  */
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import { ObjectField } from "./field-decorators/object.decorator";
6
6
  export { MapClass, MapField, DateField, ArrayField, ObjectField, };
7
7
  /**
8
8
  * npx tsc
9
- * npx ts-node src/test.ts
9
+ * npx tsx src/test.ts
10
10
  * npm version ( patch | minor | major )
11
11
  * npm publish
12
12
  */
package/dist/test.js CHANGED
@@ -7,6 +7,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  var __metadata = (this && this.__metadata) || function (k, v) {
8
8
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
9
  };
10
+ //// import "reflect-metadata";
10
11
  import { MapClass } from "./class.decorator";
11
12
  import { ArrayField } from "./field-decorators/array.decorator";
12
13
  import { DateField } from "./field-decorators/date.decorator";
@@ -16,11 +17,6 @@ import { ObjectField } from "./field-decorators/object.decorator";
16
17
  console.log("\nMAPPER FACTORY - TEST");
17
18
  console.log("\n");
18
19
  let History = class History {
19
- id;
20
- name;
21
- testControl;
22
- daysActive;
23
- testConcatenation;
24
20
  };
25
21
  __decorate([
26
22
  MapField({
@@ -57,14 +53,6 @@ History = __decorate([
57
53
  MapClass()
58
54
  ], History);
59
55
  let User = class User {
60
- id;
61
- username;
62
- name;
63
- surname;
64
- roles;
65
- employees;
66
- boss;
67
- histories;
68
56
  };
69
57
  __decorate([
70
58
  MapField({
@@ -111,6 +99,7 @@ User = __decorate([
111
99
  const emp1 = new User().from({ firstName: "Summer", lastName: "Smith" });
112
100
  const emp2 = new User().from({ firstName: "Morty", lastName: "Smith" });
113
101
  const JSONObject = {
102
+ id: "0",
114
103
  username: "god",
115
104
  firstName: "Rick",
116
105
  lastName: "Sanchez",
@@ -120,13 +109,19 @@ const JSONObject = {
120
109
  };
121
110
  //TEST constructor
122
111
  const u = new User().from(JSONObject);
123
- const constructorTest = u.username == JSONObject.username &&
112
+ console.log("\n\n\n");
113
+ console.log(u);
114
+ console.log("\n\n\n");
115
+ console.log(JSONObject);
116
+ console.log("\n\n\n");
117
+ const constructorTest = !!(u.id == JSONObject.id &&
118
+ u.username == JSONObject.username &&
124
119
  u.name == JSONObject.firstName &&
125
120
  u.surname == JSONObject.lastName &&
126
121
  u.employees?.map((emp) => emp.name == emp1.name) &&
127
122
  u.roles?.map((role) => role == "CEO") &&
128
123
  u.boss.name == "Nello" &&
129
- u.boss.surname == "Stanco";
124
+ u.boss.surname == "Stanco");
130
125
  console.log("TEST CONSTRUCTOR", constructorTest ? "✅" : "❌");
131
126
  //TEST toModel method with JS Object
132
127
  const u1 = new User().toModel(u);
@@ -167,7 +162,6 @@ const hTest2 = new History().from({
167
162
  const toModelTest4 = hTest2.toMap().test.concatenation == "resolve ";
168
163
  console.log("TEST CONCAT WITH POINT", toModelTest4 ? "✅" : "❌");
169
164
  let Test = class Test {
170
- a;
171
165
  };
172
166
  __decorate([
173
167
  MapField({
@@ -190,10 +184,10 @@ console.log("TEST TO MODEL WITH INITIALIZE", model3.a == "test to model" ? "✅"
190
184
  const model4 = model3.toMap();
191
185
  console.log("TEST TO MAP WITH INITIALIZE", model4.b.a == "test reverser" ? "✅" : "❌");
192
186
  const model5 = model3.copy();
193
- console.log("TEST COPY WITH INITIALIZE", Object.keys(model5).every((k) => model5[k] == model3[k]) ? "✅" : "❌");
187
+ console.log("TEST COPY WITH INITIALIZE", Object.keys(model5).every((k) => model5.get(k) == model3.get(k))
188
+ ? "✅"
189
+ : "❌");
194
190
  let TestFlag = class TestFlag {
195
- flTest;
196
- a;
197
191
  };
198
192
  __decorate([
199
193
  MapField({
@@ -216,7 +210,9 @@ TestFlag = __decorate([
216
210
  MapClass()
217
211
  ], TestFlag);
218
212
  const testFlagInitialize = new TestFlag().from();
219
- console.log("TEST INITIALIZE", testFlagInitialize && testFlagInitialize.a == "test transformer" ? "✅" : "❌");
213
+ console.log("TEST INITIALIZE", testFlagInitialize && testFlagInitialize.a == "test transformer"
214
+ ? "✅"
215
+ : "❌");
220
216
  const testFlag0 = new TestFlag().from();
221
217
  const testFlag0Map = testFlag0.toMap();
222
218
  console.log("TEST FLAG0", testFlag0.flTest === false && testFlag0Map.flTest == "0" ? "✅" : "❌");
@@ -227,8 +223,6 @@ const testFlag2 = new TestFlag().from({ flTest: "0" });
227
223
  const testFlag2Map = testFlag2.toMap();
228
224
  console.log("TEST FLAG2", !testFlag2.flTest && testFlag2Map.flTest == "0" ? "✅" : "❌");
229
225
  let TestWithoutMapField = class TestWithoutMapField {
230
- id;
231
- name;
232
226
  };
233
227
  TestWithoutMapField = __decorate([
234
228
  MapClass()
@@ -240,8 +234,6 @@ const JSONObject2 = {
240
234
  const testWOMF = new TestWithoutMapField().from(JSONObject2);
241
235
  console.log("TEST WITHOUT MAP FIELD", testWOMF ? "✅" : "❌");
242
236
  let ObjDecorator = class ObjDecorator {
243
- id;
244
- testObject;
245
237
  };
246
238
  __decorate([
247
239
  ObjectField(ObjDecorator),
@@ -251,9 +243,6 @@ ObjDecorator = __decorate([
251
243
  MapClass()
252
244
  ], ObjDecorator);
253
245
  let TestDecorators = class TestDecorators {
254
- date;
255
- objList;
256
- obj;
257
246
  };
258
247
  __decorate([
259
248
  DateField({ src: "dateSrc" }),
@@ -279,7 +268,7 @@ const JSONTestDecorators = {
279
268
  obj: { id: "5", testObject: { id: "6" } },
280
269
  };
281
270
  const testDecorators = new TestDecorators().from(JSONTestDecorators);
282
- console.log("TEST WITH DECORATORS", testDecorators.date.toISOString() &&
271
+ console.log("TEST WITH DECORATORS", testDecorators.date?.toISOString() &&
283
272
  testDecorators.objList?.length === 2 &&
284
273
  testDecorators.obj instanceof ObjDecorator &&
285
274
  testDecorators.objList[0] instanceof ObjDecorator &&
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Retrieve a value from an object using a string path (dot notation).
3
+ * Supports array indexing e.g. "users[0].name" or "users.0.name".
4
+ */
5
+ export declare function getValueByPath(obj: any, path: string): any;
6
+ /**
7
+ * Set a value on an object using a string path (dot notation).
8
+ * Creates nested objects/arrays if they don't exist.
9
+ */
10
+ export declare function setValueByPath(obj: any, path: string, value: any): void;
package/dist/utils.js ADDED
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Retrieve a value from an object using a string path (dot notation).
3
+ * Supports array indexing e.g. "users[0].name" or "users.0.name".
4
+ */
5
+ export function getValueByPath(obj, path) {
6
+ if (obj == null || !path)
7
+ return undefined;
8
+ // Normalize path: "a[0].b" -> "a.0.b"
9
+ const normalizedPath = path.replace(/\[(\w+)\]/g, ".$1");
10
+ const parts = normalizedPath.split(".");
11
+ let current = obj;
12
+ for (const part of parts) {
13
+ if (current == null)
14
+ return undefined;
15
+ current = current[part];
16
+ }
17
+ return current;
18
+ }
19
+ /**
20
+ * Set a value on an object using a string path (dot notation).
21
+ * Creates nested objects/arrays if they don't exist.
22
+ */
23
+ export function setValueByPath(obj, path, value) {
24
+ if (obj == null || !path)
25
+ return;
26
+ const normalizedPath = path.replace(/\[(\w+)\]/g, ".$1");
27
+ const parts = normalizedPath.split(".");
28
+ let current = obj;
29
+ for (let i = 0; i < parts.length - 1; i++) {
30
+ const part = parts[i];
31
+ // If the property doesn't exist, create it.
32
+ // Look ahead to decide if we need an array or an object.
33
+ if (current[part] == null) {
34
+ const nextPart = parts[i + 1];
35
+ // If next part is an integer, assume array.
36
+ const isNextIndex = !isNaN(parseInt(nextPart)) && isFinite(parseInt(nextPart));
37
+ current[part] = isNextIndex ? [] : {};
38
+ }
39
+ current = current[part];
40
+ }
41
+ const lastPart = parts[parts.length - 1];
42
+ current[lastPart] = value;
43
+ }
package/package.json CHANGED
@@ -1,50 +1,47 @@
1
- {
2
- "name": "mapper-factory",
3
- "version": "4.0.0",
4
- "description": "mapper for typescript object",
5
- "type": "module",
6
- "main": "dist/index.js",
7
- "types": "dist/index.d.ts",
8
- "exports": {
9
- ".": {
10
- "types": "./dist/index.d.ts",
11
- "import": "./dist/index.js"
12
- }
13
- },
14
- "files": [
15
- "/dist",
16
- "/images"
17
- ],
18
- "scripts": {
19
- "build": "npx tsc",
20
- "dev": "npx tsc --watch",
21
- "test": "npx tsx src/test.ts",
22
- "patch": "npm version patch",
23
- "minor": "npm version minor",
24
- "major": "npm version major",
25
- "deploy": "npm publish"
26
- },
27
- "repository": {
28
- "type": "git",
29
- "url": "git+https://github.com/lucaAngrisani/mapper-factory.git"
30
- },
31
- "keywords": [
32
- "mapper-ts",
33
- "mapper-factory",
34
- "mapper",
35
- "factory",
36
- "mapper factory"
37
- ],
38
- "author": "lucaAngrisani Angrigo",
39
- "license": "ISC",
40
- "bugs": {
41
- "url": "https://github.com/lucaAngrisani/mapper-factory/issues"
42
- },
43
- "homepage": "https://github.com/lucaAngrisani/mapper-factory#readme",
44
- "dependencies": {
45
- "reflect-metadata": "^0.2.2"
46
- },
47
- "devDependencies": {
48
- "typescript": "^5.7.3"
49
- }
50
- }
1
+ {
2
+ "name": "mapper-factory",
3
+ "version": "4.0.2",
4
+ "description": "mapper for typescript object",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "/dist",
16
+ "/images"
17
+ ],
18
+ "scripts": {
19
+ "build": "npx tsc",
20
+ "dev": "npx tsc --watch",
21
+ "test": "npx tsx src/test.ts",
22
+ "patch": "npm version patch",
23
+ "minor": "npm version minor",
24
+ "major": "npm version major",
25
+ "deploy": "npm publish"
26
+ },
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "git+https://github.com/lucaAngrisani/mapper-factory.git"
30
+ },
31
+ "keywords": [
32
+ "mapper-ts",
33
+ "mapper-factory",
34
+ "mapper",
35
+ "factory",
36
+ "mapper factory"
37
+ ],
38
+ "author": "lucaAngrisani Angrigo",
39
+ "license": "ISC",
40
+ "bugs": {
41
+ "url": "https://github.com/lucaAngrisani/mapper-factory/issues"
42
+ },
43
+ "homepage": "https://github.com/lucaAngrisani/mapper-factory#readme",
44
+ "devDependencies": {
45
+ "typescript": "^5.7.3"
46
+ }
47
+ }