@tsed/json-mapper 8.44.1 → 8.44.3
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/lib/esm/components/DateMapper.js +2 -1
- package/lib/esm/components/PrimitiveMapper.js +6 -4
- package/lib/esm/domain/JsonDeserializer.js +64 -27
- package/lib/esm/domain/JsonMapperCompiler.js +15 -1
- package/lib/esm/domain/JsonSerializer.js +103 -37
- package/lib/types/domain/JsonDeserializer.d.ts +25 -2
- package/lib/types/domain/JsonMapperCompiler.d.ts +7 -0
- package/lib/types/domain/JsonSerializer.d.ts +20 -1
- package/package.json +5 -5
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { __decorate } from "tslib";
|
|
2
2
|
import { JsonMapper } from "../decorators/jsonMapper.js";
|
|
3
3
|
import { isBoolean } from "@tsed/core";
|
|
4
|
+
const toISOString = Date.prototype.toISOString;
|
|
4
5
|
/**
|
|
5
6
|
* Mapper for `Date` type.
|
|
6
7
|
* @jsonmapper
|
|
@@ -17,7 +18,7 @@ let DateMapper = class DateMapper {
|
|
|
17
18
|
return new Date(data);
|
|
18
19
|
}
|
|
19
20
|
serialize(object) {
|
|
20
|
-
return object ? new Date(object).toISOString() : object;
|
|
21
|
+
return object ? (object instanceof Date ? toISOString.call(object) : new Date(object).toISOString()) : object;
|
|
21
22
|
}
|
|
22
23
|
};
|
|
23
24
|
DateMapper = __decorate([
|
|
@@ -2,7 +2,7 @@ import { __decorate } from "tslib";
|
|
|
2
2
|
import { JsonMapper } from "../decorators/jsonMapper.js";
|
|
3
3
|
import { nameOf } from "@tsed/core";
|
|
4
4
|
function isNullish(data) {
|
|
5
|
-
return
|
|
5
|
+
return data === null || data === "null";
|
|
6
6
|
}
|
|
7
7
|
/**
|
|
8
8
|
* Error thrown when a primitive conversion cannot be performed safely.
|
|
@@ -23,15 +23,17 @@ let PrimitiveMapper = class PrimitiveMapper {
|
|
|
23
23
|
return this[nameOf(ctx.type)] ? this[nameOf(ctx.type)](data, ctx) : undefined;
|
|
24
24
|
}
|
|
25
25
|
serialize(object, ctx) {
|
|
26
|
-
|
|
26
|
+
const name = nameOf(ctx?.type);
|
|
27
|
+
// inline typeof on purpose: hot path for every primitive value, cheaper than calling isObject()
|
|
28
|
+
return this[name] && typeof object !== "object" ? this[name](object, ctx) : object;
|
|
27
29
|
}
|
|
28
30
|
String(data) {
|
|
29
31
|
return data === null ? null : "" + data;
|
|
30
32
|
}
|
|
31
33
|
Boolean(data) {
|
|
32
|
-
if (
|
|
34
|
+
if (data === "true" || data === "1" || data === true)
|
|
33
35
|
return true;
|
|
34
|
-
if (
|
|
36
|
+
if (data === "false" || data === "0" || data === false)
|
|
35
37
|
return false;
|
|
36
38
|
if (isNullish(data))
|
|
37
39
|
return null;
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import { JsonMapperCompiler } from "./JsonMapperCompiler.js";
|
|
2
2
|
import { JsonParameterStore, JsonSchema, VendorKeys, getPropertiesStores, s } from "@tsed/schema";
|
|
3
|
-
import { classOf, isArray, isBoolean, isEmpty, isNil, nameOf, objectKeys } from "@tsed/core";
|
|
3
|
+
import { classOf, isArray, isBoolean, isEmpty, isFunction, isNil, nameOf, objectKeys } from "@tsed/core";
|
|
4
4
|
import { JsonMapperSettings } from "./JsonMapperSettings.js";
|
|
5
5
|
import { Writer } from "./Writer.js";
|
|
6
6
|
import { alterAfterDeserialize } from "../hooks/alterAfterDeserialize.js";
|
|
7
7
|
import { alterBeforeDeserialize } from "../hooks/alterBeforeDeserialize.js";
|
|
8
8
|
import { alterOnDeserialize } from "../hooks/alterOnDeserialize.js";
|
|
9
9
|
import { getJsonMapperTypes } from "./JsonMapperTypesContainer.js";
|
|
10
|
+
/**
|
|
11
|
+
* Whether the mapper has a callable method named after the type. A property set to another value (e.g. `undefined`
|
|
12
|
+
* to disable the conversion) falls back to `deserialize()` instead of throwing.
|
|
13
|
+
*/
|
|
14
|
+
function hasConversionMethod(mapper, name) {
|
|
15
|
+
return isFunction(mapper[name]);
|
|
16
|
+
}
|
|
10
17
|
function isDeserializable(obj, options) {
|
|
11
18
|
if ((!!options.collectionType && isNil(obj)) || obj === undefined) {
|
|
12
19
|
return false;
|
|
@@ -38,6 +45,11 @@ export class JsonDeserializer extends JsonMapperCompiler {
|
|
|
38
45
|
* @protected
|
|
39
46
|
*/
|
|
40
47
|
this.constructors = {};
|
|
48
|
+
/**
|
|
49
|
+
* The type mappers registered from the types container, with the function each one was registered with
|
|
50
|
+
* @protected
|
|
51
|
+
*/
|
|
52
|
+
this.typeMappers = new Map();
|
|
41
53
|
this.addGlobal("newInstanceOf", this.newInstanceOf.bind(this));
|
|
42
54
|
this.addGlobal("alterBeforeDeserialize", this.alterBeforeDeserialize.bind(this));
|
|
43
55
|
this.addGlobal("alterAfterDeserialize", this.alterAfterDeserialize.bind(this));
|
|
@@ -198,6 +210,32 @@ export class JsonDeserializer extends JsonMapperCompiler {
|
|
|
198
210
|
}
|
|
199
211
|
return (writer) => writer.callMapper(nestedMapper.id, varKey(key), formatOpts);
|
|
200
212
|
}
|
|
213
|
+
/**
|
|
214
|
+
* A type mapper must be registered again when it was never registered, when the mapper changed, when the mapper
|
|
215
|
+
* gained or lost the method named after the type, or when its entry was replaced or removed from the cache.
|
|
216
|
+
* The registered function reads the mapper methods at call time, so a replaced method needs no registration.
|
|
217
|
+
*/
|
|
218
|
+
isTypeMapperOutdated(model, mapper) {
|
|
219
|
+
const registered = this.typeMappers.get(model);
|
|
220
|
+
if (!registered) {
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
const mapperChanged = registered.mapper !== mapper;
|
|
224
|
+
const hasConversion = hasConversionMethod(mapper, registered.name);
|
|
225
|
+
const conversionChanged = registered.hasConversion !== hasConversion;
|
|
226
|
+
// two models with the same name (Date and "Date") share the mapper table entry: the last registered one wins
|
|
227
|
+
const entryReplaced = this.cache.get(model)?.get("typeMapper")?.fn !== registered.fn || this.mappers[registered.name] !== registered.fn;
|
|
228
|
+
return mapperChanged || conversionChanged || entryReplaced;
|
|
229
|
+
}
|
|
230
|
+
registerTypeMapper(model, mapper) {
|
|
231
|
+
const name = nameOf(model);
|
|
232
|
+
const hasConversion = hasConversionMethod(mapper, name);
|
|
233
|
+
const fn = hasConversion
|
|
234
|
+
? (value, options) => mapper[name](value, options)
|
|
235
|
+
: (value, options) => mapper.deserialize(value, { ...options, type: model });
|
|
236
|
+
this.addTypeMapper(model, fn);
|
|
237
|
+
this.typeMappers.set(model, { mapper, name, hasConversion, fn });
|
|
238
|
+
}
|
|
201
239
|
mapOptions({ groups, useAlias = true, types, ...options }) {
|
|
202
240
|
if (options.store instanceof JsonParameterStore) {
|
|
203
241
|
return this.mapOptions(mapParamStoreOptions(options.store, options));
|
|
@@ -205,17 +243,8 @@ export class JsonDeserializer extends JsonMapperCompiler {
|
|
|
205
243
|
const customMappers = {};
|
|
206
244
|
types = types || getJsonMapperTypes();
|
|
207
245
|
types.forEach((mapper, model) => {
|
|
208
|
-
if (
|
|
209
|
-
|
|
210
|
-
if (nameOf(model) in mapper) {
|
|
211
|
-
this.addTypeMapper(model, mapper[typeName].bind(mapper));
|
|
212
|
-
}
|
|
213
|
-
else {
|
|
214
|
-
this.addTypeMapper(model, (value, options) => mapper.deserialize(value, {
|
|
215
|
-
...options,
|
|
216
|
-
type: model
|
|
217
|
-
}));
|
|
218
|
-
}
|
|
246
|
+
if (model !== Array && model !== Set && model !== Map && this.isTypeMapperOutdated(model, mapper)) {
|
|
247
|
+
this.registerTypeMapper(model, mapper);
|
|
219
248
|
}
|
|
220
249
|
});
|
|
221
250
|
const strictGroups = options.strictGroups ?? JsonMapperSettings.strictGroups;
|
|
@@ -254,37 +283,45 @@ export class JsonDeserializer extends JsonMapperCompiler {
|
|
|
254
283
|
mapObject(input, options) {
|
|
255
284
|
return input;
|
|
256
285
|
}
|
|
257
|
-
|
|
286
|
+
// The collection mappers strip `id` from the options once, not once per item:
|
|
287
|
+
// `id` describes the items, it must not leak into the options of a nested mapper.
|
|
288
|
+
mapSet(input, { id, ...options }) {
|
|
258
289
|
if (isNil(input)) {
|
|
259
290
|
return input;
|
|
260
291
|
}
|
|
261
292
|
const obj = new Set();
|
|
262
|
-
objectKeys(input)
|
|
263
|
-
obj.add(this.
|
|
264
|
-
}
|
|
293
|
+
for (const key of objectKeys(input)) {
|
|
294
|
+
obj.add(this.execMapper(id, input[key], options));
|
|
295
|
+
}
|
|
265
296
|
return obj;
|
|
266
297
|
}
|
|
267
|
-
mapArray(input, options) {
|
|
298
|
+
mapArray(input, { id, ...options }) {
|
|
268
299
|
if (isNil(input)) {
|
|
269
300
|
return input;
|
|
270
301
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
302
|
+
// a single value is mapped as a one-item array
|
|
303
|
+
const list = isArray(input) ? input : [input];
|
|
304
|
+
const out = new Array(list.length);
|
|
305
|
+
for (let i = 0; i < list.length; i++) {
|
|
306
|
+
const item = list[i];
|
|
307
|
+
// keep the holes of a sparse array: the item mapper is not called for them
|
|
308
|
+
if (item === undefined && !(i in list)) {
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
out[i] = this.execMapper(id, item, options);
|
|
312
|
+
}
|
|
313
|
+
return out;
|
|
274
314
|
}
|
|
275
|
-
mapMap(input, options) {
|
|
315
|
+
mapMap(input, { id, ...options }) {
|
|
276
316
|
if (isNil(input)) {
|
|
277
317
|
return input;
|
|
278
318
|
}
|
|
279
319
|
const obj = new Map();
|
|
280
|
-
objectKeys(input)
|
|
281
|
-
obj.set(key, this.
|
|
282
|
-
}
|
|
320
|
+
for (const key of objectKeys(input)) {
|
|
321
|
+
obj.set(key, this.execMapper(id, input[key], options));
|
|
322
|
+
}
|
|
283
323
|
return obj;
|
|
284
324
|
}
|
|
285
|
-
mapItem(input, { id, ...options }) {
|
|
286
|
-
return this.execMapper(id, input, options);
|
|
287
|
-
}
|
|
288
325
|
alterBeforeDeserialize(schemaId, value, options) {
|
|
289
326
|
return alterBeforeDeserialize(value, this.schemes[schemaId], options);
|
|
290
327
|
}
|
|
@@ -11,6 +11,12 @@ export class JsonMapperCompiler {
|
|
|
11
11
|
* @protected
|
|
12
12
|
*/
|
|
13
13
|
this.cache = new Map();
|
|
14
|
+
/**
|
|
15
|
+
* Cached result of `getType()` by model. `getType()` walks the class ancestors
|
|
16
|
+
* and runs for every value serialized without a schema, so it is resolved once per model.
|
|
17
|
+
* @protected
|
|
18
|
+
*/
|
|
19
|
+
this.types = new Map();
|
|
14
20
|
/**
|
|
15
21
|
* Cached executable mappers by his id
|
|
16
22
|
* @protected
|
|
@@ -91,7 +97,7 @@ export class JsonMapperCompiler {
|
|
|
91
97
|
};
|
|
92
98
|
}
|
|
93
99
|
compile(model, groups, opts = {}) {
|
|
94
|
-
const token = isString(model) ? model : this.
|
|
100
|
+
const token = isString(model) ? model : this.resolveType(model);
|
|
95
101
|
const groupsId = this.getGroupsId(groups);
|
|
96
102
|
let storeGroups = this.cache.get(token) || this.cache.get(nameOf(token));
|
|
97
103
|
if (!storeGroups) {
|
|
@@ -124,6 +130,14 @@ export class JsonMapperCompiler {
|
|
|
124
130
|
}
|
|
125
131
|
return this.mappers[id || nameOf(classOf(value))](value, options);
|
|
126
132
|
}
|
|
133
|
+
resolveType(model) {
|
|
134
|
+
let type = this.types.get(model);
|
|
135
|
+
if (type === undefined) {
|
|
136
|
+
type = this.getType(model);
|
|
137
|
+
this.types.set(model, type);
|
|
138
|
+
}
|
|
139
|
+
return type;
|
|
140
|
+
}
|
|
127
141
|
getType(model) {
|
|
128
142
|
if (!model) {
|
|
129
143
|
return Object;
|
|
@@ -1,14 +1,27 @@
|
|
|
1
1
|
import { getPropertiesStores, s } from "@tsed/schema";
|
|
2
|
-
import { classOf, hasJsonMethod, isArray, isBoolean, isClassObject, isCollection, isDate, isMomentObject, isMongooseObject, isNil, isObject, isPrimitive, nameOf } from "@tsed/core";
|
|
2
|
+
import { classOf, hasJsonMethod, isArray, isBoolean, isClassObject, isCollection, isDate, isFunction, isMomentObject, isMongooseObject, isNil, isObject, isPrimitive, nameOf } from "@tsed/core";
|
|
3
|
+
import { DateMapper } from "../components/DateMapper.js";
|
|
4
|
+
import { PrimitiveMapper } from "../components/PrimitiveMapper.js";
|
|
3
5
|
import { JsonMapperCompiler } from "./JsonMapperCompiler.js";
|
|
4
6
|
import { JsonMapperSettings } from "./JsonMapperSettings.js";
|
|
5
7
|
import { Writer } from "./Writer.js";
|
|
6
8
|
import { alterOnSerialize } from "../hooks/alterOnSerialize.js";
|
|
7
9
|
import { getJsonMapperTypes } from "./JsonMapperTypesContainer.js";
|
|
8
|
-
import { getObjectProperties } from "../utils/getObjectProperties.js";
|
|
9
10
|
const getCollectionType = (input) => {
|
|
10
11
|
return isArray(input) ? "Array" : input instanceof Set ? "Set" : input instanceof Map ? "Map" : undefined;
|
|
11
12
|
};
|
|
13
|
+
/**
|
|
14
|
+
* Assign a mapped value on a plain output object. A `__proto__` key is defined as an own property:
|
|
15
|
+
* a plain assignment would call the `__proto__` setter, change the prototype and drop the key.
|
|
16
|
+
*/
|
|
17
|
+
function setKey(obj, key, value) {
|
|
18
|
+
if (key === "__proto__") {
|
|
19
|
+
Object.defineProperty(obj, key, { value, enumerable: true, writable: true, configurable: true });
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
obj[key] = value;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
12
25
|
function getBestType(type, obj) {
|
|
13
26
|
const dataType = classOf(obj);
|
|
14
27
|
if (dataType && !isClassObject(dataType) && !isCollection(obj)) {
|
|
@@ -19,6 +32,29 @@ function getBestType(type, obj) {
|
|
|
19
32
|
function varKey(k) {
|
|
20
33
|
return `__${k}`;
|
|
21
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* The function a type mapper is registered with. The built-in Date and primitive mappers do not read the
|
|
37
|
+
* context, so they are called without building one: a copy of the options for every Date, string and number
|
|
38
|
+
* the payload holds was most of the time of a serialization.
|
|
39
|
+
*/
|
|
40
|
+
function createTypeMapper(mapper, model, name) {
|
|
41
|
+
const kind = mapper.constructor;
|
|
42
|
+
const conversion = mapper[name];
|
|
43
|
+
if (kind === PrimitiveMapper &&
|
|
44
|
+
mapper.serialize === PrimitiveMapper.prototype.serialize &&
|
|
45
|
+
isFunction(conversion) &&
|
|
46
|
+
conversion === PrimitiveMapper.prototype[name]) {
|
|
47
|
+
// inline typeof on purpose: this runs for every primitive value, and calling isObject() here was ~15% slower on the serializer benchmark
|
|
48
|
+
return (value) => (typeof value !== "object" ? conversion.call(mapper, value) : value);
|
|
49
|
+
}
|
|
50
|
+
if (kind === DateMapper && mapper.serialize === DateMapper.prototype.serialize) {
|
|
51
|
+
return (value) => mapper.serialize(value);
|
|
52
|
+
}
|
|
53
|
+
return (value, options) => mapper.serialize(value, {
|
|
54
|
+
...options,
|
|
55
|
+
type: model
|
|
56
|
+
});
|
|
57
|
+
}
|
|
22
58
|
/**
|
|
23
59
|
* Serializes data structures according to the metadata captured by `@tsed/schema`.
|
|
24
60
|
*
|
|
@@ -28,6 +64,11 @@ function varKey(k) {
|
|
|
28
64
|
export class JsonSerializer extends JsonMapperCompiler {
|
|
29
65
|
constructor() {
|
|
30
66
|
super();
|
|
67
|
+
/**
|
|
68
|
+
* The type mappers registered from the types container, with the function each one was registered with
|
|
69
|
+
* @protected
|
|
70
|
+
*/
|
|
71
|
+
this.typeMappers = new Map();
|
|
31
72
|
this.addTypeMapper(Object, this.mapObject.bind(this));
|
|
32
73
|
this.addTypeMapper(Array, this.mapArray.bind(this));
|
|
33
74
|
this.addTypeMapper(Map, this.mapMap.bind(this));
|
|
@@ -76,15 +117,30 @@ export class JsonSerializer extends JsonMapperCompiler {
|
|
|
76
117
|
writer.add(this.mapAdditionalProperties(entity, properties));
|
|
77
118
|
return writer.return("obj").root().toString();
|
|
78
119
|
}
|
|
120
|
+
/**
|
|
121
|
+
* A type mapper must be registered again when it was never registered, when the mapper or one of the
|
|
122
|
+
* methods its function was built from changed, or when its entry was replaced or removed from the cache.
|
|
123
|
+
*/
|
|
124
|
+
isTypeMapperOutdated(model, mapper) {
|
|
125
|
+
const registered = this.typeMappers.get(model);
|
|
126
|
+
if (!registered) {
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
const mapperChanged = registered.mapper !== mapper;
|
|
130
|
+
const methodsChanged = registered.serialize !== mapper.serialize || registered.conversion !== mapper[registered.name];
|
|
131
|
+
// two models with the same name (Date and "Date") share the mapper table entry: the last registered one wins
|
|
132
|
+
const entryReplaced = this.cache.get(model)?.get("typeMapper")?.fn !== registered.fn || this.mappers[registered.name] !== registered.fn;
|
|
133
|
+
return mapperChanged || methodsChanged || entryReplaced;
|
|
134
|
+
}
|
|
79
135
|
mapOptions({ groups, useAlias = true, types, ...options }) {
|
|
80
136
|
const customMappers = {};
|
|
81
137
|
types = types || getJsonMapperTypes();
|
|
82
138
|
types.forEach((mapper, model) => {
|
|
83
|
-
if (![Array, Set, Map].includes(model)) {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
})
|
|
139
|
+
if (![Array, Set, Map].includes(model) && this.isTypeMapperOutdated(model, mapper)) {
|
|
140
|
+
const name = nameOf(model);
|
|
141
|
+
const fn = createTypeMapper(mapper, model, name);
|
|
142
|
+
this.addTypeMapper(model, fn);
|
|
143
|
+
this.typeMappers.set(model, { mapper, name, serialize: mapper.serialize, conversion: mapper[name], fn });
|
|
88
144
|
}
|
|
89
145
|
});
|
|
90
146
|
const strictGroups = options.strictGroups ?? JsonMapperSettings.strictGroups;
|
|
@@ -211,50 +267,60 @@ export class JsonSerializer extends JsonMapperCompiler {
|
|
|
211
267
|
if (hasJsonMethod(input)) {
|
|
212
268
|
return this.mapJSON(input, options);
|
|
213
269
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
if (
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
270
|
+
const obj = {};
|
|
271
|
+
// own enumerable keys only, like Object.entries(); functions and undefined values are skipped
|
|
272
|
+
for (const key in input) {
|
|
273
|
+
if (!Object.hasOwn(input, key)) {
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
const value = input[key];
|
|
277
|
+
if (value === undefined || isFunction(value)) {
|
|
278
|
+
continue;
|
|
222
279
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
[key]: mapper.fn(value, options)
|
|
227
|
-
};
|
|
228
|
-
}, {});
|
|
280
|
+
setKey(obj, key, value === null ? value : this.compile(classOf(value), options.groups).fn(value, options));
|
|
281
|
+
}
|
|
282
|
+
return obj;
|
|
229
283
|
}
|
|
230
|
-
|
|
284
|
+
// The collection mappers strip `id` from the options once, not once per item:
|
|
285
|
+
// `id` describes the items, it must not leak into the options of a nested object mapper.
|
|
286
|
+
mapSet(input, { id, ...options }) {
|
|
231
287
|
if (isNil(input)) {
|
|
232
288
|
return input;
|
|
233
289
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
290
|
+
const out = [];
|
|
291
|
+
for (const item of input) {
|
|
292
|
+
out.push(this.mapItem(item, id, options));
|
|
293
|
+
}
|
|
294
|
+
return out;
|
|
237
295
|
}
|
|
238
|
-
mapArray(input, options) {
|
|
296
|
+
mapArray(input, { id, ...options }) {
|
|
239
297
|
if (isNil(input)) {
|
|
240
298
|
return input;
|
|
241
299
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
300
|
+
// a single value is mapped as a one-item array, like the former [].concat(input)
|
|
301
|
+
const list = isArray(input) ? input : [input];
|
|
302
|
+
const out = new Array(list.length);
|
|
303
|
+
for (let i = 0; i < list.length; i++) {
|
|
304
|
+
const item = list[i];
|
|
305
|
+
// keep the holes of a sparse array, like the former .map(): the item mapper is not called for them
|
|
306
|
+
if (item === undefined && !(i in list)) {
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
out[i] = this.mapItem(item, id, options);
|
|
310
|
+
}
|
|
311
|
+
return out;
|
|
245
312
|
}
|
|
246
|
-
mapMap(input, options) {
|
|
313
|
+
mapMap(input, { id, ...options }) {
|
|
247
314
|
if (isNil(input)) {
|
|
248
315
|
return input;
|
|
249
316
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
}, {});
|
|
317
|
+
const obj = {};
|
|
318
|
+
for (const [key, item] of input) {
|
|
319
|
+
setKey(obj, key, this.mapItem(item, id, options));
|
|
320
|
+
}
|
|
321
|
+
return obj;
|
|
256
322
|
}
|
|
257
|
-
mapItem(input,
|
|
323
|
+
mapItem(input, id, options) {
|
|
258
324
|
if (!id && input) {
|
|
259
325
|
return this.compile(classOf(input), options.groups).fn(input, options);
|
|
260
326
|
}
|
|
@@ -1,6 +1,17 @@
|
|
|
1
|
-
import { CachedGroupsJsonMapper, CachedJsonMapper, JsonMapperCompiler } from "./JsonMapperCompiler.js";
|
|
1
|
+
import { CachedGroupsJsonMapper, CachedJsonMapper, JsonMapperCallback, JsonMapperCompiler } from "./JsonMapperCompiler.js";
|
|
2
2
|
import { Type } from "@tsed/core";
|
|
3
|
+
import { JsonMapperMethods } from "../interfaces/JsonMapperMethods.js";
|
|
3
4
|
import { JsonDeserializerOptions } from "./JsonDeserializerOptions.js";
|
|
5
|
+
/**
|
|
6
|
+
* A registered type mapper. `hasConversion` tells whether the mapper had a method named after the type
|
|
7
|
+
* (e.g. `PrimitiveMapper.String`) when it was registered: the function calls that method, otherwise `deserialize()`.
|
|
8
|
+
*/
|
|
9
|
+
type TypeMapperEntry = {
|
|
10
|
+
mapper: JsonMapperMethods;
|
|
11
|
+
name: string;
|
|
12
|
+
hasConversion: boolean;
|
|
13
|
+
fn: JsonMapperCallback<any>;
|
|
14
|
+
};
|
|
4
15
|
/**
|
|
5
16
|
* Deserializes raw JSON data into class instances based on schema metadata,
|
|
6
17
|
* handling collections, discriminators, hooks, and registered type mappers.
|
|
@@ -11,6 +22,11 @@ export declare class JsonDeserializer extends JsonMapperCompiler<JsonDeserialize
|
|
|
11
22
|
* @protected
|
|
12
23
|
*/
|
|
13
24
|
protected constructors: Record<string, Type<any>>;
|
|
25
|
+
/**
|
|
26
|
+
* The type mappers registered from the types container, with the function each one was registered with
|
|
27
|
+
* @protected
|
|
28
|
+
*/
|
|
29
|
+
protected typeMappers: Map<any, TypeMapperEntry>;
|
|
14
30
|
constructor();
|
|
15
31
|
map<Model = any>(input: any, options?: JsonDeserializerOptions): Model;
|
|
16
32
|
compile(model: Type<any>, groups: false | string[]): CachedJsonMapper<JsonDeserializerOptions>;
|
|
@@ -26,14 +42,21 @@ export declare class JsonDeserializer extends JsonMapperCompiler<JsonDeserialize
|
|
|
26
42
|
private mapDiscriminator;
|
|
27
43
|
private mapProperty;
|
|
28
44
|
private getPropertyFiller;
|
|
45
|
+
/**
|
|
46
|
+
* A type mapper must be registered again when it was never registered, when the mapper changed, when the mapper
|
|
47
|
+
* gained or lost the method named after the type, or when its entry was replaced or removed from the cache.
|
|
48
|
+
* The registered function reads the mapper methods at call time, so a replaced method needs no registration.
|
|
49
|
+
*/
|
|
50
|
+
private isTypeMapperOutdated;
|
|
51
|
+
private registerTypeMapper;
|
|
29
52
|
private mapOptions;
|
|
30
53
|
private mapAdditionalProperties;
|
|
31
54
|
private mapObject;
|
|
32
55
|
private mapSet;
|
|
33
56
|
private mapArray;
|
|
34
57
|
private mapMap;
|
|
35
|
-
private mapItem;
|
|
36
58
|
private alterBeforeDeserialize;
|
|
37
59
|
private alterAfterDeserialize;
|
|
38
60
|
private mapGenericsOptions;
|
|
39
61
|
}
|
|
62
|
+
export {};
|
|
@@ -27,6 +27,12 @@ export declare abstract class JsonMapperCompiler<Options extends Record<string,
|
|
|
27
27
|
* @protected
|
|
28
28
|
*/
|
|
29
29
|
protected cache: Map<string | Type<any>, CachedGroupsJsonMapper<Options>>;
|
|
30
|
+
/**
|
|
31
|
+
* Cached result of `getType()` by model. `getType()` walks the class ancestors
|
|
32
|
+
* and runs for every value serialized without a schema, so it is resolved once per model.
|
|
33
|
+
* @protected
|
|
34
|
+
*/
|
|
35
|
+
protected types: Map<Type<any>, Type<any>>;
|
|
30
36
|
/**
|
|
31
37
|
* Cached executable mappers by his id
|
|
32
38
|
* @protected
|
|
@@ -67,6 +73,7 @@ export declare abstract class JsonMapperCompiler<Options extends Record<string,
|
|
|
67
73
|
protected abstract map(input: any, options: Options): any;
|
|
68
74
|
protected abstract alterValue(schemaId: string, value: any, options: Options): any;
|
|
69
75
|
protected abstract createMapper(model: Type<any>, id: string, groups: false | string[]): string;
|
|
76
|
+
protected resolveType(model: Type<any>): Type<any>;
|
|
70
77
|
protected getType(model: Type<any>): Type<any> | MapConstructor | ObjectConstructor | ArrayConstructor | SetConstructor;
|
|
71
78
|
protected alterIgnore(id: string, options: Options): any;
|
|
72
79
|
protected alterGroups(schema: JsonSchema, groups: false | string[]): boolean;
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import { Type } from "@tsed/core";
|
|
2
|
-
import {
|
|
2
|
+
import { JsonMapperMethods } from "../interfaces/JsonMapperMethods.js";
|
|
3
|
+
import { JsonMapperCompiler, JsonMapperCallback } from "./JsonMapperCompiler.js";
|
|
3
4
|
import { JsonSerializerOptions } from "./JsonSerializerOptions.js";
|
|
5
|
+
type TypeMapperEntry = {
|
|
6
|
+
mapper: JsonMapperMethods;
|
|
7
|
+
name: string;
|
|
8
|
+
serialize: any;
|
|
9
|
+
conversion: any;
|
|
10
|
+
fn: JsonMapperCallback<any>;
|
|
11
|
+
};
|
|
4
12
|
/**
|
|
5
13
|
* Serializes data structures according to the metadata captured by `@tsed/schema`.
|
|
6
14
|
*
|
|
@@ -8,10 +16,20 @@ import { JsonSerializerOptions } from "./JsonSerializerOptions.js";
|
|
|
8
16
|
* registered type mappers (e.g., components in `src/components`).
|
|
9
17
|
*/
|
|
10
18
|
export declare class JsonSerializer extends JsonMapperCompiler<JsonSerializerOptions> {
|
|
19
|
+
/**
|
|
20
|
+
* The type mappers registered from the types container, with the function each one was registered with
|
|
21
|
+
* @protected
|
|
22
|
+
*/
|
|
23
|
+
protected typeMappers: Map<any, TypeMapperEntry>;
|
|
11
24
|
constructor();
|
|
12
25
|
map(input: any, options?: JsonSerializerOptions): any;
|
|
13
26
|
protected alterValue(schemaId: string, value: any, options: JsonSerializerOptions): any;
|
|
14
27
|
protected createMapper(model: Type<any>, id: string, groups: false | string[]): string;
|
|
28
|
+
/**
|
|
29
|
+
* A type mapper must be registered again when it was never registered, when the mapper or one of the
|
|
30
|
+
* methods its function was built from changed, or when its entry was replaced or removed from the cache.
|
|
31
|
+
*/
|
|
32
|
+
private isTypeMapperOutdated;
|
|
15
33
|
private mapOptions;
|
|
16
34
|
private mapProperty;
|
|
17
35
|
private getPropertyFiller;
|
|
@@ -26,3 +44,4 @@ export declare class JsonSerializer extends JsonMapperCompiler<JsonSerializerOpt
|
|
|
26
44
|
private mapItem;
|
|
27
45
|
private mapJSON;
|
|
28
46
|
}
|
|
47
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tsed/json-mapper",
|
|
3
|
-
"version": "8.44.
|
|
3
|
+
"version": "8.44.3",
|
|
4
4
|
"description": "Json mapper module for Ts.ED Framework",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"TypeScript",
|
|
@@ -41,10 +41,10 @@
|
|
|
41
41
|
"tslib": ">=2.7.0"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
|
-
"@tsed/barrels": "8.44.
|
|
45
|
-
"@tsed/core": "8.44.
|
|
46
|
-
"@tsed/schema": "8.44.
|
|
47
|
-
"@tsed/typescript": "8.44.
|
|
44
|
+
"@tsed/barrels": "8.44.3",
|
|
45
|
+
"@tsed/core": "8.44.3",
|
|
46
|
+
"@tsed/schema": "8.44.3",
|
|
47
|
+
"@tsed/typescript": "8.44.3",
|
|
48
48
|
"temporal-polyfill": "1.0.1",
|
|
49
49
|
"typescript": "6.0.2",
|
|
50
50
|
"vitest": "4.1.4"
|