@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hg-ts/serialize",
3
- "version": "0.5.16",
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.16",
22
- "@hg-ts/exception": "0.5.16",
23
- "@hg-ts/linter": "0.5.16",
24
- "@hg-ts/tests": "0.5.16",
25
- "@hg-ts/types": "0.5.16",
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.16",
37
+ "@hg-ts/exception": "0.5.18",
38
38
  "reflect-metadata": "*",
39
39
  "tslib": "*",
40
40
  "vitest": "*"
@@ -0,0 +1,5 @@
1
+ export const classes = new Map<string, Class>();
2
+
3
+ export function addAdditionalClass(classToTransform: Class<any, any[]>): void {
4
+ classes.set(classToTransform.name, classToTransform);
5
+ }
@@ -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,7 @@
1
+ import { BaseException } from '@hg-ts/exception';
2
+
3
+ export class DeserializeUnknownPrototypeException extends BaseException {
4
+ public constructor(ctorName: string) {
5
+ super(`Deserialize error. Constructor with name ${ctorName} not found`);
6
+ }
7
+ }
@@ -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
+ }
@@ -0,0 +1,3 @@
1
+ export * from './deserialize-unknown-prototype.exception.js';
2
+ export * from './deserialize.exception.js';
3
+ export * from './serialize.exception.js';
@@ -0,0 +1,7 @@
1
+ import { BaseException } from '@hg-ts/exception';
2
+
3
+ export class SerializeException extends BaseException {
4
+ public constructor(type: string) {
5
+ super(`Unsupported type for serialization: ${type}`);
6
+ }
7
+ }
package/src/index.ts ADDED
@@ -0,0 +1,7 @@
1
+ export * from './types.js';
2
+
3
+ export { addAdditionalClass } from './additional-classes.js';
4
+ export * from './serialize.js';
5
+ export * from './deserialize.js';
6
+
7
+ export * from './exceptions/index.js';
@@ -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,6 @@
1
+ export class Example {
2
+ public readonly value: string;
3
+ public constructor(value: string) {
4
+ this.value = value;
5
+ }
6
+ }
@@ -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;