mapper-factory 4.0.0 → 4.0.1

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.
@@ -1,4 +1,3 @@
1
- import 'reflect-metadata';
2
1
  import { ClassType } from '../types';
3
2
  export declare const MAP_FIELD: unique symbol;
4
3
  export interface MapperMetadata<T = any> {
@@ -1,4 +1,3 @@
1
- import 'reflect-metadata';
2
1
  export const MAP_FIELD = Symbol('MAP_FIELD');
3
2
  export function isClass(func) {
4
3
  return (typeof func === 'function' &&
@@ -11,7 +10,7 @@ export const MapField = ({ transformer, reverser, src, initialize = false, } = {
11
10
  return (target, property) => {
12
11
  const classConstructor = target.constructor;
13
12
  const propertyName = property.toString();
14
- const metadata = Reflect.getMetadata(MAP_FIELD, classConstructor) || {};
13
+ const metadata = classConstructor[MAP_FIELD] || {};
15
14
  // create new object reference to avoid this issue: https://github.com/rbuckton/reflect-metadata/issues/62
16
15
  const newMetadata = { ...metadata };
17
16
  const previousValues = metadata[propertyName];
@@ -22,14 +21,14 @@ export const MapField = ({ transformer, reverser, src, initialize = false, } = {
22
21
  transformer,
23
22
  reverser,
24
23
  };
25
- Reflect.defineMetadata(MAP_FIELD, newMetadata, classConstructor);
24
+ classConstructor[MAP_FIELD] = newMetadata;
26
25
  };
27
26
  };
28
27
  export const getMapFieldMetadataList = (target) => {
29
- return Reflect.getMetadata(MAP_FIELD, getPrototype(target));
28
+ return getPrototype(target)[MAP_FIELD];
30
29
  };
31
30
  export const hasMapFieldMetadataList = (target) => {
32
- return Reflect.hasMetadata(MAP_FIELD, getPrototype(target));
31
+ return !!getPrototype(target)[MAP_FIELD];
33
32
  };
34
33
  export const getMapFieldMetadata = (target, propertyName) => {
35
34
  const metadata = getMapFieldMetadataList(target);
@@ -39,6 +38,6 @@ export const getMapFieldMetadata = (target, propertyName) => {
39
38
  return metadata[name];
40
39
  };
41
40
  export const hasMapFieldMetadata = (target, propertyName) => {
42
- const metadata = Reflect.getMetadata(MAP_FIELD, getPrototype(target));
41
+ const metadata = getPrototype(target)[MAP_FIELD];
43
42
  return metadata && !!metadata[propertyName];
44
43
  };
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,35 @@ 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
+ finalValue = value;
33
+ }
34
+ if (finalValue !== undefined) {
35
+ setValueByPath(obj, src, finalValue);
69
36
  }
70
37
  });
38
+ }
71
39
  return obj;
72
40
  }
73
41
  /**
@@ -111,8 +79,7 @@ export function filled() {
111
79
  * @returns Value of the property
112
80
  */
113
81
  export function get(path) {
114
- const props = path.replace(/\[(\w+)\]/g, ".$1").split(".");
115
- return props.reduce((acc, prop) => acc && acc[prop], this);
82
+ return getValueByPath(this, path);
116
83
  }
117
84
  /**
118
85
  * SET property value from a string path.
@@ -121,14 +88,7 @@ export function get(path) {
121
88
  * @param value Value of the property
122
89
  */
123
90
  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;
91
+ setValueByPath(this, path, value);
132
92
  }
133
93
  /**
134
94
  * Deep copy of the object caller
@@ -143,77 +103,50 @@ export function copy() {
143
103
  */
144
104
  export function from(object) {
145
105
  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);
106
+ const mappedSrcRoots = new Set();
107
+ // 1. Process Metadata
108
+ if (metadataList && object) {
109
+ Object.keys(metadataList).forEach((key) => {
110
+ const meta = metadataList[key];
111
+ const src = meta.src || key;
112
+ // Identify root property for this mapping to exclude it from direct copy later
113
+ const normalizedPath = src.replace(/\[(\w+)\]/g, ".$1");
114
+ const root = normalizedPath.split(".")[0];
115
+ mappedSrcRoots.add(root);
116
+ const value = getValueByPath(object, src);
117
+ // Only process if value exists in source (matches old behavior and avoids infinite recursion on undefined)
118
+ if (value !== undefined) {
119
+ if (meta.transformer) {
120
+ const transformed = meta.transformer(value, object);
121
+ if (transformed !== undefined) {
122
+ this[key] = transformed;
194
123
  }
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
124
  }
204
125
  else {
205
- setProperty(propertyName, object[propertyName], object);
126
+ this[key] = value;
206
127
  }
207
128
  }
208
129
  });
209
- // Initialize properties with "initialize" metadata
210
- metadataList &&
130
+ }
131
+ // 2. Process Remaining Properties (Direct Copy)
132
+ // Only if they are not part of a mapped source root
133
+ if (object) {
134
+ Object.keys(object).forEach(prop => {
135
+ if (!mappedSrcRoots.has(prop)) {
136
+ this[prop] = object[prop];
137
+ }
138
+ });
139
+ }
140
+ // 3. Initialize properties with "initialize" metadata
141
+ if (metadataList) {
211
142
  Object.keys(metadataList).forEach((metaName) => {
212
- if (metadataList[metaName]?.initialize &&
213
- metadataList[metaName]?.transformer &&
143
+ const meta = metadataList[metaName];
144
+ if (meta?.initialize &&
145
+ meta?.transformer &&
214
146
  this[metaName] === undefined) {
215
- this[metaName] = metadataList[metaName].transformer(null, object);
147
+ this[metaName] = meta.transformer(null, object);
216
148
  }
217
149
  });
150
+ }
218
151
  return this;
219
152
  }
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";
@@ -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,6 +1,6 @@
1
1
  {
2
2
  "name": "mapper-factory",
3
- "version": "4.0.0",
3
+ "version": "4.0.1",
4
4
  "description": "mapper for typescript object",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -41,9 +41,6 @@
41
41
  "url": "https://github.com/lucaAngrisani/mapper-factory/issues"
42
42
  },
43
43
  "homepage": "https://github.com/lucaAngrisani/mapper-factory#readme",
44
- "dependencies": {
45
- "reflect-metadata": "^0.2.2"
46
- },
47
44
  "devDependencies": {
48
45
  "typescript": "^5.7.3"
49
46
  }