@tsed/json-mapper 8.44.2 → 8.44.4
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/domain/JsonDeserializer.js +48 -31
- package/lib/esm/domain/JsonMapperCompiler.js +37 -0
- package/lib/esm/domain/JsonSerializer.js +16 -28
- package/lib/types/domain/JsonDeserializer.d.ts +0 -1
- package/lib/types/domain/JsonMapperCompiler.d.ts +29 -0
- package/lib/types/domain/JsonSerializer.d.ts +1 -20
- package/package.json +5 -5
|
@@ -1,12 +1,35 @@
|
|
|
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
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The registered function reads the mapper methods at call time, so a replaced method needs no registration.
|
|
19
|
+
*/
|
|
20
|
+
function createTypeMapperEntry(mapper, model, name) {
|
|
21
|
+
const hasConversion = hasConversionMethod(mapper, name);
|
|
22
|
+
const fn = hasConversion
|
|
23
|
+
? (value, options) => mapper[name](value, options)
|
|
24
|
+
: (value, options) => mapper.deserialize(value, { ...options, type: model });
|
|
25
|
+
return { mapper, name, hasConversion, fn };
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The function must be built again when the mapper gained or lost the method named after the type.
|
|
29
|
+
*/
|
|
30
|
+
function hasTypeMapperChanged(entry, mapper) {
|
|
31
|
+
return entry.hasConversion !== hasConversionMethod(mapper, entry.name);
|
|
32
|
+
}
|
|
10
33
|
function isDeserializable(obj, options) {
|
|
11
34
|
if ((!!options.collectionType && isNil(obj)) || obj === undefined) {
|
|
12
35
|
return false;
|
|
@@ -203,21 +226,7 @@ export class JsonDeserializer extends JsonMapperCompiler {
|
|
|
203
226
|
return this.mapOptions(mapParamStoreOptions(options.store, options));
|
|
204
227
|
}
|
|
205
228
|
const customMappers = {};
|
|
206
|
-
types
|
|
207
|
-
types.forEach((mapper, model) => {
|
|
208
|
-
if (![Array, Set, Map].includes(model)) {
|
|
209
|
-
const typeName = nameOf(model);
|
|
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
|
-
}
|
|
219
|
-
}
|
|
220
|
-
});
|
|
229
|
+
this.syncTypeMappers(types || getJsonMapperTypes(), createTypeMapperEntry, hasTypeMapperChanged);
|
|
221
230
|
const strictGroups = options.strictGroups ?? JsonMapperSettings.strictGroups;
|
|
222
231
|
return {
|
|
223
232
|
...options,
|
|
@@ -254,37 +263,45 @@ export class JsonDeserializer extends JsonMapperCompiler {
|
|
|
254
263
|
mapObject(input, options) {
|
|
255
264
|
return input;
|
|
256
265
|
}
|
|
257
|
-
|
|
266
|
+
// The collection mappers strip `id` from the options once, not once per item:
|
|
267
|
+
// `id` describes the items, it must not leak into the options of a nested mapper.
|
|
268
|
+
mapSet(input, { id, ...options }) {
|
|
258
269
|
if (isNil(input)) {
|
|
259
270
|
return input;
|
|
260
271
|
}
|
|
261
272
|
const obj = new Set();
|
|
262
|
-
objectKeys(input)
|
|
263
|
-
obj.add(this.
|
|
264
|
-
}
|
|
273
|
+
for (const key of objectKeys(input)) {
|
|
274
|
+
obj.add(this.execMapper(id, input[key], options));
|
|
275
|
+
}
|
|
265
276
|
return obj;
|
|
266
277
|
}
|
|
267
|
-
mapArray(input, options) {
|
|
278
|
+
mapArray(input, { id, ...options }) {
|
|
268
279
|
if (isNil(input)) {
|
|
269
280
|
return input;
|
|
270
281
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
282
|
+
// a single value is mapped as a one-item array
|
|
283
|
+
const list = isArray(input) ? input : [input];
|
|
284
|
+
const out = new Array(list.length);
|
|
285
|
+
for (let i = 0; i < list.length; i++) {
|
|
286
|
+
const item = list[i];
|
|
287
|
+
// keep the holes of a sparse array: the item mapper is not called for them
|
|
288
|
+
if (item === undefined && !(i in list)) {
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
out[i] = this.execMapper(id, item, options);
|
|
292
|
+
}
|
|
293
|
+
return out;
|
|
274
294
|
}
|
|
275
|
-
mapMap(input, options) {
|
|
295
|
+
mapMap(input, { id, ...options }) {
|
|
276
296
|
if (isNil(input)) {
|
|
277
297
|
return input;
|
|
278
298
|
}
|
|
279
299
|
const obj = new Map();
|
|
280
|
-
objectKeys(input)
|
|
281
|
-
obj.set(key, this.
|
|
282
|
-
}
|
|
300
|
+
for (const key of objectKeys(input)) {
|
|
301
|
+
obj.set(key, this.execMapper(id, input[key], options));
|
|
302
|
+
}
|
|
283
303
|
return obj;
|
|
284
304
|
}
|
|
285
|
-
mapItem(input, { id, ...options }) {
|
|
286
|
-
return this.execMapper(id, input, options);
|
|
287
|
-
}
|
|
288
305
|
alterBeforeDeserialize(schemaId, value, options) {
|
|
289
306
|
return alterBeforeDeserialize(value, this.schemes[schemaId], options);
|
|
290
307
|
}
|
|
@@ -17,6 +17,11 @@ export class JsonMapperCompiler {
|
|
|
17
17
|
* @protected
|
|
18
18
|
*/
|
|
19
19
|
this.types = new Map();
|
|
20
|
+
/**
|
|
21
|
+
* The type mappers registered from the types container, with the function each one was registered with
|
|
22
|
+
* @protected
|
|
23
|
+
*/
|
|
24
|
+
this.typeMappers = new Map();
|
|
20
25
|
/**
|
|
21
26
|
* Cached executable mappers by his id
|
|
22
27
|
* @protected
|
|
@@ -124,6 +129,38 @@ export class JsonMapperCompiler {
|
|
|
124
129
|
}
|
|
125
130
|
return storeGroups.get(groupsId);
|
|
126
131
|
}
|
|
132
|
+
/**
|
|
133
|
+
* Register the type mappers of the types container (Array, Set and Map excepted: the compiler maps them itself).
|
|
134
|
+
* A type mapper is registered again only when it is outdated, so `map()` does not rebuild them on every call.
|
|
135
|
+
*
|
|
136
|
+
* @param types The type mappers by model.
|
|
137
|
+
* @param create Build the entry, and the function, a type mapper is registered with.
|
|
138
|
+
* @param hasChanged Tell whether the mapper changed since its entry was built, beyond being replaced by another mapper.
|
|
139
|
+
*/
|
|
140
|
+
syncTypeMappers(types, create, hasChanged) {
|
|
141
|
+
types.forEach((mapper, model) => {
|
|
142
|
+
if (model !== Array && model !== Set && model !== Map && this.isTypeMapperOutdated(model, mapper, hasChanged)) {
|
|
143
|
+
const entry = create(mapper, model, nameOf(model));
|
|
144
|
+
this.addTypeMapper(model, entry.fn);
|
|
145
|
+
this.typeMappers.set(model, entry);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* A type mapper must be registered again when it was never registered, when the mapper was replaced or changed,
|
|
151
|
+
* or when its entry was replaced or removed from the cache.
|
|
152
|
+
*/
|
|
153
|
+
isTypeMapperOutdated(model, mapper, hasChanged) {
|
|
154
|
+
const registered = this.typeMappers.get(model);
|
|
155
|
+
if (!registered) {
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
return (registered.mapper !== mapper ||
|
|
159
|
+
hasChanged(registered, mapper) ||
|
|
160
|
+
// two models with the same name (Date and "Date") share the mapper table entry: the last registered one wins
|
|
161
|
+
this.cache.get(model)?.get("typeMapper")?.fn !== registered.fn ||
|
|
162
|
+
this.mappers[registered.name] !== registered.fn);
|
|
163
|
+
}
|
|
127
164
|
execMapper(id, value, options) {
|
|
128
165
|
if (isObjectID(value)) {
|
|
129
166
|
return value.toString();
|
|
@@ -55,6 +55,21 @@ function createTypeMapper(mapper, model, name) {
|
|
|
55
55
|
type: model
|
|
56
56
|
});
|
|
57
57
|
}
|
|
58
|
+
function createTypeMapperEntry(mapper, model, name) {
|
|
59
|
+
return {
|
|
60
|
+
mapper,
|
|
61
|
+
name,
|
|
62
|
+
serialize: mapper.serialize,
|
|
63
|
+
conversion: mapper[name],
|
|
64
|
+
fn: createTypeMapper(mapper, model, name)
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* The function of a type mapper is built from its `serialize` and conversion methods: it must be built again when one changed.
|
|
69
|
+
*/
|
|
70
|
+
function hasTypeMapperChanged(entry, mapper) {
|
|
71
|
+
return entry.serialize !== mapper.serialize || entry.conversion !== mapper[entry.name];
|
|
72
|
+
}
|
|
58
73
|
/**
|
|
59
74
|
* Serializes data structures according to the metadata captured by `@tsed/schema`.
|
|
60
75
|
*
|
|
@@ -64,11 +79,6 @@ function createTypeMapper(mapper, model, name) {
|
|
|
64
79
|
export class JsonSerializer extends JsonMapperCompiler {
|
|
65
80
|
constructor() {
|
|
66
81
|
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();
|
|
72
82
|
this.addTypeMapper(Object, this.mapObject.bind(this));
|
|
73
83
|
this.addTypeMapper(Array, this.mapArray.bind(this));
|
|
74
84
|
this.addTypeMapper(Map, this.mapMap.bind(this));
|
|
@@ -117,31 +127,9 @@ export class JsonSerializer extends JsonMapperCompiler {
|
|
|
117
127
|
writer.add(this.mapAdditionalProperties(entity, properties));
|
|
118
128
|
return writer.return("obj").root().toString();
|
|
119
129
|
}
|
|
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
|
-
const entryReplaced = this.cache.get(model)?.get("typeMapper")?.fn !== registered.fn;
|
|
132
|
-
return mapperChanged || methodsChanged || entryReplaced;
|
|
133
|
-
}
|
|
134
130
|
mapOptions({ groups, useAlias = true, types, ...options }) {
|
|
135
131
|
const customMappers = {};
|
|
136
|
-
types
|
|
137
|
-
types.forEach((mapper, model) => {
|
|
138
|
-
if (![Array, Set, Map].includes(model) && this.isTypeMapperOutdated(model, mapper)) {
|
|
139
|
-
const name = nameOf(model);
|
|
140
|
-
const fn = createTypeMapper(mapper, model, name);
|
|
141
|
-
this.addTypeMapper(model, fn);
|
|
142
|
-
this.typeMappers.set(model, { mapper, name, serialize: mapper.serialize, conversion: mapper[name], fn });
|
|
143
|
-
}
|
|
144
|
-
});
|
|
132
|
+
this.syncTypeMappers(types || getJsonMapperTypes(), createTypeMapperEntry, hasTypeMapperChanged);
|
|
145
133
|
const strictGroups = options.strictGroups ?? JsonMapperSettings.strictGroups;
|
|
146
134
|
return {
|
|
147
135
|
...options,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Type } from "@tsed/core";
|
|
2
2
|
import { JsonSchema } from "@tsed/schema";
|
|
3
|
+
import type { JsonMapperMethods } from "../interfaces/JsonMapperMethods.js";
|
|
3
4
|
/**
|
|
4
5
|
* Signature exposed by compiled mappers. They accept an input value plus contextual options
|
|
5
6
|
* and return the serialized/deserialized result.
|
|
@@ -16,6 +17,15 @@ export type CachedJsonMapper<Options> = {
|
|
|
16
17
|
* Cached mapper registry keyed by the groups fingerprint generated for a schema.
|
|
17
18
|
*/
|
|
18
19
|
export type CachedGroupsJsonMapper<Options> = Map<string, CachedJsonMapper<Options>>;
|
|
20
|
+
/**
|
|
21
|
+
* A type mapper registered from the types container: the mapper, the name of its model and the function it was
|
|
22
|
+
* registered with. Subclasses add the values their outdated check compares.
|
|
23
|
+
*/
|
|
24
|
+
export type TypeMapperEntry = {
|
|
25
|
+
mapper: JsonMapperMethods;
|
|
26
|
+
name: string;
|
|
27
|
+
fn: JsonMapperCallback<any>;
|
|
28
|
+
};
|
|
19
29
|
/**
|
|
20
30
|
* Base compiler that turns schema metadata into executable (de)serialization functions.
|
|
21
31
|
* Subclasses supply `map`, `alterValue`, and `createMapper` implementations to specialize
|
|
@@ -33,6 +43,11 @@ export declare abstract class JsonMapperCompiler<Options extends Record<string,
|
|
|
33
43
|
* @protected
|
|
34
44
|
*/
|
|
35
45
|
protected types: Map<Type<any>, Type<any>>;
|
|
46
|
+
/**
|
|
47
|
+
* The type mappers registered from the types container, with the function each one was registered with
|
|
48
|
+
* @protected
|
|
49
|
+
*/
|
|
50
|
+
protected typeMappers: Map<any, TypeMapperEntry>;
|
|
36
51
|
/**
|
|
37
52
|
* Cached executable mappers by his id
|
|
38
53
|
* @protected
|
|
@@ -69,6 +84,20 @@ export declare abstract class JsonMapperCompiler<Options extends Record<string,
|
|
|
69
84
|
compile(model: Type<any> | string, groups: false | string[], opts?: {
|
|
70
85
|
mapper?: any;
|
|
71
86
|
}): CachedJsonMapper<Options>;
|
|
87
|
+
/**
|
|
88
|
+
* Register the type mappers of the types container (Array, Set and Map excepted: the compiler maps them itself).
|
|
89
|
+
* A type mapper is registered again only when it is outdated, so `map()` does not rebuild them on every call.
|
|
90
|
+
*
|
|
91
|
+
* @param types The type mappers by model.
|
|
92
|
+
* @param create Build the entry, and the function, a type mapper is registered with.
|
|
93
|
+
* @param hasChanged Tell whether the mapper changed since its entry was built, beyond being replaced by another mapper.
|
|
94
|
+
*/
|
|
95
|
+
protected syncTypeMappers<Entry extends TypeMapperEntry>(types: Map<Type | Symbol | string, JsonMapperMethods>, create: (mapper: JsonMapperMethods, model: any, name: string) => Entry, hasChanged: (entry: Entry, mapper: JsonMapperMethods) => boolean): void;
|
|
96
|
+
/**
|
|
97
|
+
* A type mapper must be registered again when it was never registered, when the mapper was replaced or changed,
|
|
98
|
+
* or when its entry was replaced or removed from the cache.
|
|
99
|
+
*/
|
|
100
|
+
private isTypeMapperOutdated;
|
|
72
101
|
protected execMapper(id: string, value: any, options: Options): any;
|
|
73
102
|
protected abstract map(input: any, options: Options): any;
|
|
74
103
|
protected abstract alterValue(schemaId: string, value: any, options: Options): any;
|
|
@@ -1,14 +1,6 @@
|
|
|
1
1
|
import { Type } from "@tsed/core";
|
|
2
|
-
import {
|
|
3
|
-
import { JsonMapperCompiler, JsonMapperCallback } from "./JsonMapperCompiler.js";
|
|
2
|
+
import { JsonMapperCompiler } from "./JsonMapperCompiler.js";
|
|
4
3
|
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
|
-
};
|
|
12
4
|
/**
|
|
13
5
|
* Serializes data structures according to the metadata captured by `@tsed/schema`.
|
|
14
6
|
*
|
|
@@ -16,20 +8,10 @@ type TypeMapperEntry = {
|
|
|
16
8
|
* registered type mappers (e.g., components in `src/components`).
|
|
17
9
|
*/
|
|
18
10
|
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>;
|
|
24
11
|
constructor();
|
|
25
12
|
map(input: any, options?: JsonSerializerOptions): any;
|
|
26
13
|
protected alterValue(schemaId: string, value: any, options: JsonSerializerOptions): any;
|
|
27
14
|
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;
|
|
33
15
|
private mapOptions;
|
|
34
16
|
private mapProperty;
|
|
35
17
|
private getPropertyFiller;
|
|
@@ -44,4 +26,3 @@ export declare class JsonSerializer extends JsonMapperCompiler<JsonSerializerOpt
|
|
|
44
26
|
private mapItem;
|
|
45
27
|
private mapJSON;
|
|
46
28
|
}
|
|
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.4",
|
|
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.4",
|
|
45
|
+
"@tsed/core": "8.44.4",
|
|
46
|
+
"@tsed/schema": "8.44.4",
|
|
47
|
+
"@tsed/typescript": "8.44.4",
|
|
48
48
|
"temporal-polyfill": "1.0.1",
|
|
49
49
|
"typescript": "6.0.2",
|
|
50
50
|
"vitest": "4.1.4"
|