@hg-ts/serialize 0.5.16 → 0.5.18
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/package.json +7 -7
- package/src/additional-classes.ts +5 -0
- package/src/deserialize.ts +72 -0
- package/src/exceptions/deserialize-unknown-prototype.exception.ts +7 -0
- package/src/exceptions/deserialize.exception.ts +9 -0
- package/src/exceptions/index.ts +3 -0
- package/src/exceptions/serialize.exception.ts +7 -0
- package/src/index.ts +7 -0
- package/src/serialize.ts +117 -0
- package/src/tests/example.ts +6 -0
- package/src/tests/serialization.test.ts +140 -0
- package/src/types.ts +70 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hg-ts/serialize",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.18",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -18,11 +18,11 @@
|
|
|
18
18
|
"test:dev": "vitest watch"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
|
-
"@hg-ts-config/typescript": "0.5.
|
|
22
|
-
"@hg-ts/exception": "0.5.
|
|
23
|
-
"@hg-ts/linter": "0.5.
|
|
24
|
-
"@hg-ts/tests": "0.5.
|
|
25
|
-
"@hg-ts/types": "0.5.
|
|
21
|
+
"@hg-ts-config/typescript": "0.5.18",
|
|
22
|
+
"@hg-ts/exception": "0.5.18",
|
|
23
|
+
"@hg-ts/linter": "0.5.18",
|
|
24
|
+
"@hg-ts/tests": "0.5.18",
|
|
25
|
+
"@hg-ts/types": "0.5.18",
|
|
26
26
|
"@types/node": "22.19.1",
|
|
27
27
|
"@vitest/coverage-v8": "4.0.14",
|
|
28
28
|
"eslint": "9.18.0",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"vitest": "4.0.14"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
|
-
"@hg-ts/exception": "0.5.
|
|
37
|
+
"@hg-ts/exception": "0.5.18",
|
|
38
38
|
"reflect-metadata": "*",
|
|
39
39
|
"tslib": "*",
|
|
40
40
|
"vitest": "*"
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { classes } from './additional-classes.js';
|
|
2
|
+
import {
|
|
3
|
+
DeserializeException,
|
|
4
|
+
DeserializeUnknownPrototypeException,
|
|
5
|
+
} from './exceptions/index.js';
|
|
6
|
+
import type {
|
|
7
|
+
SerializedObject,
|
|
8
|
+
SerializedValue,
|
|
9
|
+
} from './types.js';
|
|
10
|
+
|
|
11
|
+
function deserializeObject(
|
|
12
|
+
{ value, prototypeNameList }: SerializedObject,
|
|
13
|
+
): unknown {
|
|
14
|
+
let targetClass: Class = Object;
|
|
15
|
+
const [preferredClassName = null] = prototypeNameList.filter(name => name !== Object.name);
|
|
16
|
+
|
|
17
|
+
if (preferredClassName) {
|
|
18
|
+
const foundCtor = classes.get(preferredClassName);
|
|
19
|
+
|
|
20
|
+
if (!foundCtor) {
|
|
21
|
+
throw new DeserializeUnknownPrototypeException(preferredClassName);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
targetClass = foundCtor;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const result: Record<string, unknown> = Object.create(targetClass.prototype);
|
|
28
|
+
|
|
29
|
+
return Object.keys(value)
|
|
30
|
+
.reduce<Record<string, unknown>>((result, key) => {
|
|
31
|
+
result[key] = deserializeValue(value[key]!);
|
|
32
|
+
|
|
33
|
+
return result;
|
|
34
|
+
}, result);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function deserializeValue(value: SerializedValue): unknown {
|
|
38
|
+
switch (value.type) {
|
|
39
|
+
case 'boolean':
|
|
40
|
+
case 'string':
|
|
41
|
+
case 'number':
|
|
42
|
+
return value.value;
|
|
43
|
+
case 'buffer':
|
|
44
|
+
return Buffer.from(value.value, 'hex');
|
|
45
|
+
case 'bigint':
|
|
46
|
+
return BigInt(value.value);
|
|
47
|
+
case 'null':
|
|
48
|
+
return null;
|
|
49
|
+
case 'date':
|
|
50
|
+
return new Date(value.value);
|
|
51
|
+
case 'array':
|
|
52
|
+
return value.value.map(item => deserializeValue(item));
|
|
53
|
+
case 'set':
|
|
54
|
+
return new Set(value.value.map(item => deserializeValue(item)));
|
|
55
|
+
case 'map':
|
|
56
|
+
return new Map(value.value.map(item => [
|
|
57
|
+
deserializeValue(item.key),
|
|
58
|
+
deserializeValue(item.value),
|
|
59
|
+
]));
|
|
60
|
+
case 'object':
|
|
61
|
+
return deserializeObject(value);
|
|
62
|
+
|
|
63
|
+
default: {
|
|
64
|
+
const error: never = value;
|
|
65
|
+
throw new DeserializeException(error);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function deserialize(value: SerializedValue): unknown {
|
|
71
|
+
return deserializeValue(value);
|
|
72
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { BaseException } from '@hg-ts/exception';
|
|
2
|
+
|
|
3
|
+
import type { SerializedValue } from '../types.js';
|
|
4
|
+
|
|
5
|
+
export class DeserializeException extends BaseException {
|
|
6
|
+
public constructor(value: SerializedValue) {
|
|
7
|
+
super(`Error for deserialize value: ${JSON.stringify(value)}`);
|
|
8
|
+
}
|
|
9
|
+
}
|
package/src/index.ts
ADDED
package/src/serialize.ts
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { SerializeException } from './exceptions/index.js';
|
|
2
|
+
import type {
|
|
3
|
+
SerializedArray,
|
|
4
|
+
SerializedObject,
|
|
5
|
+
SerializedValue,
|
|
6
|
+
} from './types.js';
|
|
7
|
+
|
|
8
|
+
export function getPrototypeListNames(value: Record<string | symbol, unknown>): string[] {
|
|
9
|
+
const result: string[] = [];
|
|
10
|
+
let nextPrototype = Object.getPrototypeOf(value);
|
|
11
|
+
|
|
12
|
+
while (nextPrototype !== null) {
|
|
13
|
+
result.push(nextPrototype.constructor.name);
|
|
14
|
+
|
|
15
|
+
nextPrototype = Object.getPrototypeOf(nextPrototype);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return result;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function serializeArray(value: unknown[]): SerializedArray {
|
|
22
|
+
return {
|
|
23
|
+
type: 'array',
|
|
24
|
+
value: value.map(item => serializeValue(item)),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function serializeRecord(value: Record<string | symbol, unknown>): SerializedObject {
|
|
29
|
+
const symbolKeys = Object.getOwnPropertySymbols(value);
|
|
30
|
+
|
|
31
|
+
if (symbolKeys.length > 0) {
|
|
32
|
+
throw new SerializeException('symbol key not allowed');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const fields = Object.keys(value)
|
|
36
|
+
.reduce<Record<string, SerializedValue>>((result, key) => {
|
|
37
|
+
result[key as string] = serializeValue(value[key]);
|
|
38
|
+
return result;
|
|
39
|
+
}, {});
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
type: 'object',
|
|
43
|
+
prototypeNameList: getPrototypeListNames(value),
|
|
44
|
+
value: fields,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function serializeObject(value: object | null): SerializedValue {
|
|
49
|
+
if (value === null) {
|
|
50
|
+
return { type: 'null' };
|
|
51
|
+
}
|
|
52
|
+
if (value instanceof Date) {
|
|
53
|
+
return {
|
|
54
|
+
type: 'date',
|
|
55
|
+
value: value.getTime(),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
if (value instanceof Buffer) {
|
|
59
|
+
return {
|
|
60
|
+
type: 'buffer',
|
|
61
|
+
value: value.toString('hex'),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
if (value instanceof Map) {
|
|
65
|
+
return {
|
|
66
|
+
type: 'map',
|
|
67
|
+
value: [...value.entries()].map(([key, value]) => ({
|
|
68
|
+
key: serializeValue(key),
|
|
69
|
+
value: serializeValue(value),
|
|
70
|
+
})),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
if (value instanceof Set) {
|
|
74
|
+
return {
|
|
75
|
+
type: 'set',
|
|
76
|
+
value: [...value.values()].map(item => serializeValue(item)),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
if (Array.isArray(value)) {
|
|
80
|
+
return serializeArray(value);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return serializeRecord(value as any);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function serializeValue(value: unknown): SerializedValue {
|
|
87
|
+
switch (typeof value) {
|
|
88
|
+
case 'boolean':
|
|
89
|
+
return {
|
|
90
|
+
value,
|
|
91
|
+
type: 'boolean',
|
|
92
|
+
};
|
|
93
|
+
case 'bigint':
|
|
94
|
+
return {
|
|
95
|
+
type: 'bigint',
|
|
96
|
+
value: String(value),
|
|
97
|
+
};
|
|
98
|
+
case 'string':
|
|
99
|
+
return {
|
|
100
|
+
value,
|
|
101
|
+
type: 'string',
|
|
102
|
+
};
|
|
103
|
+
case 'number':
|
|
104
|
+
return {
|
|
105
|
+
value,
|
|
106
|
+
type: 'number',
|
|
107
|
+
};
|
|
108
|
+
case 'object':
|
|
109
|
+
return serializeObject(value as any);
|
|
110
|
+
default:
|
|
111
|
+
throw new SerializeException(typeof value);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function serialize(value: unknown): SerializedValue {
|
|
116
|
+
return serializeValue(value);
|
|
117
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Describe,
|
|
3
|
+
expect,
|
|
4
|
+
ExpectException,
|
|
5
|
+
Suite,
|
|
6
|
+
Test,
|
|
7
|
+
} from '@hg-ts/tests';
|
|
8
|
+
import { addAdditionalClass } from '../additional-classes.js';
|
|
9
|
+
import { deserialize } from '../deserialize.js';
|
|
10
|
+
import {
|
|
11
|
+
DeserializeException,
|
|
12
|
+
DeserializeUnknownPrototypeException,
|
|
13
|
+
SerializeException,
|
|
14
|
+
} from '../exceptions/index.js';
|
|
15
|
+
import { serialize } from '../serialize.js';
|
|
16
|
+
import { SerializedObject } from '../types.js';
|
|
17
|
+
import { Example } from './example.js';
|
|
18
|
+
|
|
19
|
+
@Describe()
|
|
20
|
+
export class SerializationTestSuite extends Suite {
|
|
21
|
+
@Test()
|
|
22
|
+
public async serializeNull(): Promise<void> {
|
|
23
|
+
this.test(null);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@Test()
|
|
27
|
+
public async serializeNumber(): Promise<void> {
|
|
28
|
+
this.test(123);
|
|
29
|
+
this.test(12.35);
|
|
30
|
+
this.test(0);
|
|
31
|
+
this.test(-12.35);
|
|
32
|
+
this.test(-123);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
@Test()
|
|
36
|
+
public async serializeString(): Promise<void> {
|
|
37
|
+
this.test('random string');
|
|
38
|
+
this.test('');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@Test()
|
|
42
|
+
public async serializeSet(): Promise<void> {
|
|
43
|
+
this.test(new Set(['foo', 'bar']));
|
|
44
|
+
this.test(new Set());
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@Test()
|
|
48
|
+
public async serializeMap(): Promise<void> {
|
|
49
|
+
this.test(new Map<unknown, unknown>([
|
|
50
|
+
['foo', 'bar'],
|
|
51
|
+
[1, 'string'],
|
|
52
|
+
['string', Buffer.from('some string')],
|
|
53
|
+
]));
|
|
54
|
+
this.test(new Map());
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@Test()
|
|
58
|
+
public async serializeDate(): Promise<void> {
|
|
59
|
+
this.test(new Date());
|
|
60
|
+
this.test(new Date('2024-01-01'));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@Test()
|
|
64
|
+
public async serializeBigint(): Promise<void> {
|
|
65
|
+
this.test(1235n * 1_000_000_000_000_000_000_000_000n);
|
|
66
|
+
this.test(123n);
|
|
67
|
+
this.test(0n);
|
|
68
|
+
this.test(-123n);
|
|
69
|
+
this.test(-1235n * 1_000_000_000_000_000_000_000_000n);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@Test()
|
|
73
|
+
public async serializeBuffer(): Promise<void> {
|
|
74
|
+
this.test(Buffer.from('sdfsdgdfgdfg'));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
@Test()
|
|
78
|
+
public async serializeInstance(): Promise<void> {
|
|
79
|
+
addAdditionalClass(Example);
|
|
80
|
+
const test = new Example(Math.random().toString());
|
|
81
|
+
this.test(test);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
@Test()
|
|
85
|
+
@ExpectException(DeserializeUnknownPrototypeException)
|
|
86
|
+
public async deserializeInstanceError(): Promise<void> {
|
|
87
|
+
addAdditionalClass(Example);
|
|
88
|
+
const test = new Example(Math.random().toString());
|
|
89
|
+
|
|
90
|
+
const serialized = serialize(test) as SerializedObject;
|
|
91
|
+
|
|
92
|
+
serialized.prototypeNameList.unshift('UnknownClass');
|
|
93
|
+
deserialize(serialized);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
@Test()
|
|
97
|
+
public async deserializeWithoutNameList(): Promise<void> {
|
|
98
|
+
const test = new Example(Math.random().toString());
|
|
99
|
+
|
|
100
|
+
const serialized = serialize(test) as SerializedObject;
|
|
101
|
+
|
|
102
|
+
serialized.prototypeNameList = [];
|
|
103
|
+
expect(deserialize(serialized)).toMatchObject(test);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
@Test()
|
|
107
|
+
@ExpectException(DeserializeException)
|
|
108
|
+
public async deserializeFunction(): Promise<void> {
|
|
109
|
+
deserialize((() => true) as any);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
@Test()
|
|
113
|
+
public async serializeArray(): Promise<void> {
|
|
114
|
+
this.test([1, 2, 3]);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
@Test()
|
|
118
|
+
@ExpectException(SerializeException)
|
|
119
|
+
public async serializeSymbol(): Promise<void> {
|
|
120
|
+
this.test(Symbol('name'));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
@Test()
|
|
124
|
+
@ExpectException(SerializeException)
|
|
125
|
+
public async serializeSymbolKey(): Promise<void> {
|
|
126
|
+
this.test({ [Symbol('name')]: 'value' });
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
@Test()
|
|
130
|
+
public async serializeBoolean(): Promise<void> {
|
|
131
|
+
this.test(true);
|
|
132
|
+
this.test(false);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
protected test(expected: unknown): void {
|
|
136
|
+
const value = deserialize(serialize(expected));
|
|
137
|
+
|
|
138
|
+
expect(value).toEqual(expected);
|
|
139
|
+
}
|
|
140
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export type SerializedNumber = {
|
|
2
|
+
type: 'number';
|
|
3
|
+
value: number;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
export type SerializedString = {
|
|
7
|
+
type: 'string';
|
|
8
|
+
value: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type SerializedBoolean = {
|
|
12
|
+
type: 'boolean';
|
|
13
|
+
value: boolean;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type SerializedBigint = {
|
|
17
|
+
type: 'bigint';
|
|
18
|
+
value: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type SerializedDate = {
|
|
22
|
+
type: 'date';
|
|
23
|
+
value: number;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type SerializedArray = {
|
|
27
|
+
type: 'array';
|
|
28
|
+
value: SerializedValue[];
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export type SerializedBuffer = {
|
|
32
|
+
type: 'buffer';
|
|
33
|
+
value: string;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export type SerializedObject = {
|
|
37
|
+
type: 'object';
|
|
38
|
+
prototypeNameList: string[];
|
|
39
|
+
value: Record<string, SerializedValue>;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type SerializedMap = {
|
|
43
|
+
type: 'map';
|
|
44
|
+
value: {
|
|
45
|
+
key: SerializedValue;
|
|
46
|
+
value: SerializedValue;
|
|
47
|
+
}[];
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export type SerializedSet = {
|
|
51
|
+
type: 'set';
|
|
52
|
+
value: SerializedValue[];
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export type SerializedNull = {
|
|
56
|
+
type: 'null';
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export type SerializedValue =
|
|
60
|
+
| SerializedArray
|
|
61
|
+
| SerializedBigint
|
|
62
|
+
| SerializedBoolean
|
|
63
|
+
| SerializedDate
|
|
64
|
+
| SerializedMap
|
|
65
|
+
| SerializedNull
|
|
66
|
+
| SerializedBuffer
|
|
67
|
+
| SerializedNumber
|
|
68
|
+
| SerializedObject
|
|
69
|
+
| SerializedSet
|
|
70
|
+
| SerializedString;
|