@wwawing/util 4.1.0-save-bug-fix.based-on.3.12.17.p.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.
package/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 1996-2025 NAO
4
+ Copyright (c) 2015-2025 WWA Wing Team
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in
14
+ all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ THE SOFTWARE.
package/jest.config.ts ADDED
@@ -0,0 +1,27 @@
1
+ /*
2
+ * For a detailed explanation regarding each configuration property and type check, visit:
3
+ * https://jestjs.io/docs/configuration
4
+ */
5
+ export default {
6
+ clearMocks: true,
7
+ moduleFileExtensions: [
8
+ "js",
9
+ "ts",
10
+ "json",
11
+ ],
12
+ roots: [
13
+ "<rootDir>/src"
14
+ ],
15
+ testEnvironment: "jsdom",
16
+ testPathIgnorePatterns: [
17
+ "/node_modules/"
18
+ ],
19
+ testRegex: ["__tests__/.*|\\.test\\.(js|ts)"],
20
+ transform: {
21
+ "^.+\\.(j|t)s$": ["@swc/jest", require("../../swcConfig")]
22
+ },
23
+ transformIgnorePatterns: [
24
+ "/node_modules/",
25
+ ],
26
+ unmockedModulePathPatterns: ["node_modules/*"],
27
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,102 @@
1
+ import { convertMapToObject } from "../mapToObject";
2
+ describe("convertMapToObject", () => {
3
+ it("プリミティブを変換すると、同じ内容のプリミティブになる", () => {
4
+ expect(convertMapToObject(true)).toStrictEqual(true);
5
+ expect(convertMapToObject(false)).toStrictEqual(false);
6
+ expect(convertMapToObject(42)).toStrictEqual(42);
7
+ expect(convertMapToObject(1e99)).toStrictEqual(1e99);
8
+ expect(convertMapToObject(BigInt("12345678909876543210"))).toStrictEqual(BigInt("12345678909876543210"));
9
+ expect(convertMapToObject("🍣")).toStrictEqual("🍣");
10
+ expect(convertMapToObject(undefined)).toBeUndefined();
11
+ expect(convertMapToObject(null)).toBeNull();
12
+ });
13
+ it("配列を変換すると、同じ内容の配列になる", () => {
14
+ expect(convertMapToObject([1, 2, 3])).toStrictEqual([1, 2, 3]);
15
+ });
16
+ it("Mapを変換すると、オブジェクトになる", () => {
17
+ const result = convertMapToObject(new Map([
18
+ ["foo", "bar"],
19
+ ["hoge", 31415],
20
+ ]));
21
+ expect(result).toMatchObject({
22
+ foo: "bar",
23
+ hoge: 31415,
24
+ });
25
+ });
26
+ it("Mapを変換したとき、オブジェクトになっても同じ参照は維持される", () => {
27
+ const refA = new Map([
28
+ ["foo", new Map([["bar", null]])],
29
+ ]);
30
+ const result = convertMapToObject(new Map([
31
+ ["ref1", refA],
32
+ ["ref2", refA],
33
+ [
34
+ "ref3",
35
+ new Map([
36
+ ["foo", new Map([["bar", null]])],
37
+ ]),
38
+ ],
39
+ ]));
40
+ expect(result.ref1 === result.ref2).toBeTruthy();
41
+ expect(result.ref1 === result.ref3).toBeFalsy();
42
+ expect(result.ref2 === result.ref3).toBeFalsy();
43
+ });
44
+ it("循環参照を変換できる", () => {
45
+ const refA = new Map();
46
+ refA.set("next", refA);
47
+ const result = convertMapToObject(refA);
48
+ expect(result.next === result).toBeTruthy();
49
+ });
50
+ it("空Mapを変換できる", () => {
51
+ const result = convertMapToObject(new Map());
52
+ expect(result).toMatchObject({});
53
+ });
54
+ it("配列とMapネストしていても変換できる", () => {
55
+ var _a, _b, _c;
56
+ const refA = new Map([
57
+ ["foo", new Map([["bar", null]])],
58
+ ]);
59
+ const refB = [
60
+ new Map([
61
+ ["ref1", refA],
62
+ ["ref2", refA],
63
+ [
64
+ "ref3",
65
+ new Map([
66
+ ["foo", new Map([["bar", null]])],
67
+ ]),
68
+ ],
69
+ [
70
+ "ref4",
71
+ [
72
+ new Map([
73
+ [
74
+ "test1",
75
+ new Map([
76
+ ["test2", new Map([["test3", new Map()]])],
77
+ ]),
78
+ ],
79
+ ]),
80
+ ],
81
+ ],
82
+ ]),
83
+ refA,
84
+ ];
85
+ (_a = refA.get("foo")) === null || _a === void 0 ? void 0 : _a.set("bar", refB);
86
+ const result = convertMapToObject(refB);
87
+ expect(result).toBeInstanceOf(Array);
88
+ const result0 = result[0];
89
+ expect(result0.ref1 === result0.ref2).toBeTruthy();
90
+ expect(result0.ref1 === result0.ref3).toBeFalsy();
91
+ expect(result0.ref2 === result0.ref3).toBeFalsy();
92
+ expect(result0.ref1 === result[1]).toBeTruthy();
93
+ expect(((_c = (_b = result0.ref1) === null || _b === void 0 ? void 0 : _b.foo) === null || _c === void 0 ? void 0 : _c.bar) === result).toBeTruthy();
94
+ expect(result0.ref4).toMatchObject([{
95
+ test1: {
96
+ test2: {
97
+ test3: {},
98
+ },
99
+ },
100
+ }]);
101
+ });
102
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,90 @@
1
+ import { convertObjectToMap } from "../objectToMap";
2
+ describe("convertObjectToMap", () => {
3
+ it("プリミティブを変換すると、同じ内容のプリミティブになる", () => {
4
+ expect(convertObjectToMap(true)).toStrictEqual(true);
5
+ expect(convertObjectToMap(false)).toStrictEqual(false);
6
+ expect(convertObjectToMap(42)).toStrictEqual(42);
7
+ expect(convertObjectToMap(1e+99)).toStrictEqual(1e+99);
8
+ expect(convertObjectToMap(BigInt("12345678909876543210"))).toStrictEqual(BigInt("12345678909876543210"));
9
+ expect(convertObjectToMap("🍣")).toStrictEqual("🍣");
10
+ expect(convertObjectToMap(undefined)).toBeUndefined();
11
+ expect(convertObjectToMap(null)).toBeNull();
12
+ });
13
+ it("配列を変換すると、同じ内容の配列になる", () => {
14
+ expect(convertObjectToMap([1, 2, 3])).toStrictEqual([1, 2, 3]);
15
+ });
16
+ it("オブジェクトを変換すると、Mapになる", () => {
17
+ const result = convertObjectToMap({
18
+ foo: "bar",
19
+ hoge: 31415
20
+ });
21
+ expect(result).toBeInstanceOf(Map);
22
+ expect(result.get("foo")).toEqual("bar");
23
+ expect(result.get("hoge")).toEqual(31415);
24
+ });
25
+ it("オブジェクトを変換したとき、Mapになっても同じ参照は維持される", () => {
26
+ const refA = { foo: { bar: null } };
27
+ const result = convertObjectToMap({
28
+ ref1: refA,
29
+ ref2: refA,
30
+ ref3: { foo: { bar: null } },
31
+ });
32
+ expect(result).toBeInstanceOf(Map);
33
+ expect(result.get("ref1") === result.get("ref2")).toBeTruthy();
34
+ expect(result.get("ref1") === result.get("ref3")).toBeFalsy();
35
+ expect(result.get("ref2") === result.get("ref3")).toBeFalsy();
36
+ });
37
+ it("循環参照を変換できる", () => {
38
+ const refA = { next: null };
39
+ refA.next = refA;
40
+ const result = convertObjectToMap(refA);
41
+ expect(result).toBeInstanceOf(Map);
42
+ expect(result.get("next") === result).toBeTruthy();
43
+ });
44
+ it("空オブジェクトを変換できる", () => {
45
+ const result = convertObjectToMap({});
46
+ expect(result).toBeInstanceOf(Map);
47
+ expect(result.size).toBe(0);
48
+ });
49
+ it("配列とオブジェクトがネストしていても変換できる", () => {
50
+ var _a, _b;
51
+ const refA = { foo: { bar: null } };
52
+ const refB = [{
53
+ ref1: refA,
54
+ ref2: refA,
55
+ ref3: { foo: { bar: null } },
56
+ ref4: [{ test1: { test2: { test3: {} } } }]
57
+ }, refA];
58
+ refA.foo.bar = refB;
59
+ const result = convertObjectToMap(refB);
60
+ expect(result).toBeInstanceOf(Array);
61
+ expect(result[0]).toBeInstanceOf(Map);
62
+ expect(result[0]).toBeInstanceOf(Map);
63
+ expect(result[0].get("ref1") === result[0].get("ref2")).toBeTruthy();
64
+ expect(result[0].get("ref1") === result[0].get("ref3")).toBeFalsy();
65
+ expect(result[0].get("ref2") === result[0].get("ref3")).toBeFalsy();
66
+ expect(result[0].get("ref1") === result[1]).toBeTruthy();
67
+ const resultRef1 = result[0].get("ref1");
68
+ if (!(resultRef1 instanceof Map)) {
69
+ fail("変換結果の ref1 が Map になっていません!");
70
+ }
71
+ const resultFoo = resultRef1.get("foo");
72
+ if (!(resultFoo instanceof Map)) {
73
+ fail("変換結果の foo が Map になっていません!");
74
+ }
75
+ expect(resultFoo.get("bar") === result).toBeTruthy();
76
+ const resultRef4 = result[0].get("ref4");
77
+ if (!(resultRef4 instanceof Array)) {
78
+ fail("変換結果の ref4 が Map になっていません!");
79
+ }
80
+ const resultRef4_0 = resultRef4[0];
81
+ if (!(resultRef4_0 instanceof Map)) {
82
+ fail("変換結果の ref4[0] が Map になっていません!");
83
+ }
84
+ const maybeEmptyMap = (_b = (_a = resultRef4_0.get("test1")) === null || _a === void 0 ? void 0 : _a.get("test2")) === null || _b === void 0 ? void 0 : _b.get("test3");
85
+ if (!(maybeEmptyMap instanceof Map)) {
86
+ fail("変換結果の ref4[0].test1.test2.test3 が Map になっていません!");
87
+ }
88
+ expect(maybeEmptyMap.size).toBe(0);
89
+ });
90
+ });
@@ -0,0 +1 @@
1
+ export type Primitive = number | string | boolean | bigint | undefined | null;
@@ -0,0 +1 @@
1
+ export {};
package/lib/index.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { convertObjectToMap, type ObjectToMap } from "./objectToMap";
2
+ export { convertMapToObject, } from "./mapToObject";
package/lib/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { convertObjectToMap } from "./objectToMap";
2
+ export { convertMapToObject, } from "./mapToObject";
@@ -0,0 +1,7 @@
1
+ import type { Primitive } from "./_common";
2
+ type SeenRefMap = WeakMap<Map<string, unknown> | Array<unknown>, unknown>;
3
+ export declare function convertMapToObject<T extends Primitive>(input: T, seen?: SeenRefMap): T;
4
+ export declare function convertMapToObject<T extends ReadonlyArray<unknown>>(input: T, seen?: SeenRefMap): ReadonlyArray<unknown>;
5
+ export declare function convertMapToObject<T extends Array<unknown>>(input: T, seen?: SeenRefMap): Array<unknown>;
6
+ export declare function convertMapToObject<T extends Map<string, unknown>>(input: T, seen?: SeenRefMap): Record<string, unknown>;
7
+ export {};
@@ -0,0 +1,31 @@
1
+ export function convertMapToObject(input, seen = new WeakMap()) {
2
+ if (typeof input === "symbol") {
3
+ throw new TypeError("symbol は変換対象外です");
4
+ }
5
+ if (input instanceof Map) {
6
+ if (seen.has(input)) {
7
+ return seen.get(input);
8
+ }
9
+ const obj = Object.create(null);
10
+ seen.set(input, obj);
11
+ for (const [key, value] of input.entries()) {
12
+ obj[key] = convertMapToObject(value, seen);
13
+ }
14
+ return obj;
15
+ }
16
+ if (Array.isArray(input)) {
17
+ if (seen.has(input)) {
18
+ return seen.get(input);
19
+ }
20
+ const arr = [];
21
+ seen.set(input, arr);
22
+ for (const item of input) {
23
+ arr.push(convertMapToObject(item, seen));
24
+ }
25
+ return arr;
26
+ }
27
+ if (typeof input === 'object' && input !== null) {
28
+ throw new TypeError("Mapおよび配列でないオブジェクトは変換対象外です");
29
+ }
30
+ return input;
31
+ }
@@ -0,0 +1,10 @@
1
+ import type { Primitive } from "./_common";
2
+ type SeenRefMap = WeakMap<object, unknown>;
3
+ export type ObjectToMap<T> = UnionToIntersection<T extends Primitive ? T : T extends ReadonlyArray<infer U> ? ReadonlyArray<ObjectToMap<U>> : T extends Array<infer U> ? Array<ObjectToMap<U>> : T extends object ? Record<never, never> extends T ? Map<string, never> : {
4
+ [K in keyof T]: Map<K, ObjectToMap<T[K]>>;
5
+ }[keyof T] : never>;
6
+ type UnionToIntersection<U> = (U extends any ? (x: U) => void : never) extends (x: infer R) => void ? R : never;
7
+ export declare function convertObjectToMap<T extends Primitive>(input: T, seen?: SeenRefMap): T;
8
+ export declare function convertObjectToMap<T extends Array<unknown>>(input: T, seen?: SeenRefMap): T;
9
+ export declare function convertObjectToMap<T extends Exclude<object, null>>(input: T, seen?: SeenRefMap): ObjectToMap<T>;
10
+ export {};
@@ -0,0 +1,28 @@
1
+ export function convertObjectToMap(input, seen = new WeakMap()) {
2
+ if (typeof input === "symbol") {
3
+ throw new TypeError("symbol は変換対象外です");
4
+ }
5
+ if (typeof input !== "object" || input === null) {
6
+ return input;
7
+ }
8
+ if (seen.has(input)) {
9
+ return seen.get(input);
10
+ }
11
+ if (Array.isArray(input)) {
12
+ const arr = [];
13
+ seen.set(input, arr);
14
+ for (const item of input) {
15
+ arr.push(convertObjectToMap(item, seen));
16
+ }
17
+ return arr;
18
+ }
19
+ const map = new Map();
20
+ seen.set(input, map);
21
+ for (const [key, value] of Object.entries(input)) {
22
+ if (typeof key !== "string") {
23
+ throw new TypeError("文字列以外のキーは変換できません");
24
+ }
25
+ map.set(key, convertObjectToMap(value, seen));
26
+ }
27
+ return map;
28
+ }
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@wwawing/util",
3
+ "version": "4.1.0-save-bug-fix.based-on.3.12.17.p.1",
4
+ "description": "WWA Wing utility package",
5
+ "main": "lib/index.js",
6
+ "types": "lib/index.d.ts",
7
+ "scripts": {
8
+ "build": "shx rm -rf lib && tsc -p .",
9
+ "test": "jest"
10
+ },
11
+ "author": "WWA Wing Team",
12
+ "license": "MIT",
13
+ "devDependencies": {
14
+ "@swc/core": "^1.10.4",
15
+ "@swc/jest": "^0.2.37",
16
+ "jest": "^29.5.0",
17
+ "jest-cli": "^29.5.0",
18
+ "jest-environment-jsdom": "^29.5.0",
19
+ "shelljs": "^0.9.2",
20
+ "shx": "^0.4.0",
21
+ "typescript": "^5.8.3"
22
+ },
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
26
+ "gitHead": "eeda902be8443fb2eb84694501dbe7a2552cb5ce"
27
+ }
@@ -0,0 +1,116 @@
1
+ import { convertMapToObject } from "../mapToObject";
2
+
3
+ describe("convertMapToObject", () => {
4
+ it("プリミティブを変換すると、同じ内容のプリミティブになる", () => {
5
+ expect(convertMapToObject(true)).toStrictEqual(true);
6
+ expect(convertMapToObject(false)).toStrictEqual(false);
7
+ expect(convertMapToObject(42)).toStrictEqual(42);
8
+ expect(convertMapToObject(1e99)).toStrictEqual(1e99);
9
+ expect(convertMapToObject(BigInt("12345678909876543210"))).toStrictEqual(
10
+ BigInt("12345678909876543210")
11
+ );
12
+ expect(convertMapToObject("🍣")).toStrictEqual("🍣");
13
+ expect(convertMapToObject(undefined)).toBeUndefined();
14
+ expect(convertMapToObject(null)).toBeNull();
15
+ });
16
+ it("配列を変換すると、同じ内容の配列になる", () => {
17
+ expect(convertMapToObject([1, 2, 3])).toStrictEqual([1, 2, 3]);
18
+ });
19
+ it("Mapを変換すると、オブジェクトになる", () => {
20
+ const result = convertMapToObject(
21
+ new Map<string, unknown>([
22
+ ["foo", "bar"],
23
+ ["hoge", 31415],
24
+ ])
25
+ );
26
+ expect(result).toMatchObject({
27
+ foo: "bar",
28
+ hoge: 31415,
29
+ });
30
+ });
31
+ it("Mapを変換したとき、オブジェクトになっても同じ参照は維持される", () => {
32
+ const refA = new Map<string, unknown>([
33
+ ["foo", new Map<string, unknown>([["bar", null]])],
34
+ ]);
35
+ const result = convertMapToObject(
36
+ new Map<string, unknown>([
37
+ ["ref1", refA],
38
+ ["ref2", refA],
39
+ [
40
+ "ref3",
41
+ new Map<string, unknown>([
42
+ ["foo", new Map<string, unknown>([["bar", null]])],
43
+ ]),
44
+ ], // 異なる参照
45
+ ])
46
+ );
47
+ expect(result.ref1 === result.ref2).toBeTruthy();
48
+ expect(result.ref1 === result.ref3).toBeFalsy();
49
+ expect(result.ref2 === result.ref3).toBeFalsy();
50
+ });
51
+ it("循環参照を変換できる", () => {
52
+ const refA = new Map();
53
+ refA.set("next", refA);
54
+ const result = convertMapToObject(refA);
55
+ expect(result.next === result).toBeTruthy();
56
+ });
57
+ it("空Mapを変換できる", () => {
58
+ const result = convertMapToObject(new Map());
59
+ expect(result).toMatchObject({});
60
+ });
61
+ it("配列とMapネストしていても変換できる", () => {
62
+ const refA = new Map<"foo", Map<"bar", unknown>>([
63
+ ["foo", new Map([["bar", null]])],
64
+ ]);
65
+ const refB = [
66
+ new Map<"ref1" | "ref2" | "ref3" | "ref4", unknown>([
67
+ ["ref1", refA],
68
+ ["ref2", refA],
69
+ [
70
+ "ref3",
71
+ new Map<string, unknown>([
72
+ ["foo", new Map([["bar", null]])],
73
+ ]),
74
+ ],
75
+ [
76
+ "ref4",
77
+ [
78
+ new Map<"test1", Map<"test2", Map<"test3", unknown>>>([
79
+ [
80
+ "test1",
81
+ new Map([
82
+ ["test2", new Map([["test3", new Map()]])],
83
+ ]),
84
+ ],
85
+ ]),
86
+ ],
87
+ ],
88
+ ]),
89
+ refA,
90
+ ];
91
+ refA.get("foo")?.set("bar", refB);
92
+ const result = convertMapToObject(refB);
93
+ expect(result).toBeInstanceOf(Array);
94
+ // convertMapToObject の戻り値の型は妥協しているので仕方ない
95
+ // また、Map に一度変換している関係で, 値は Optional になる
96
+ // 本来は値を評価する前にバリデーションをするのが望ましいが、まぁテストなので...
97
+ const result0 = result[0] as {
98
+ ref1?: { foo?: { bar?: unknown } };
99
+ ref2?: { foo?: { bar?: unknown } };
100
+ ref3?: { foo?: { bar?: unknown } };
101
+ ref4?: unknown;
102
+ };
103
+ expect(result0.ref1 === result0.ref2).toBeTruthy();
104
+ expect(result0.ref1 === result0.ref3).toBeFalsy();
105
+ expect(result0.ref2 === result0.ref3).toBeFalsy();
106
+ expect(result0.ref1 === result[1]).toBeTruthy();
107
+ expect(result0.ref1?.foo?.bar === result).toBeTruthy();
108
+ expect(result0.ref4).toMatchObject([{
109
+ test1: {
110
+ test2: {
111
+ test3: {},
112
+ },
113
+ },
114
+ }]);
115
+ });
116
+ });
@@ -0,0 +1,91 @@
1
+ import { convertObjectToMap } from "../objectToMap"
2
+
3
+ describe("convertObjectToMap", () => {
4
+ it("プリミティブを変換すると、同じ内容のプリミティブになる", () => {
5
+ expect(convertObjectToMap(true)).toStrictEqual(true);
6
+ expect(convertObjectToMap(false)).toStrictEqual(false);
7
+ expect(convertObjectToMap(42)).toStrictEqual(42);
8
+ expect(convertObjectToMap(1e+99)).toStrictEqual(1e+99);
9
+ expect(convertObjectToMap(BigInt("12345678909876543210"))).toStrictEqual(BigInt("12345678909876543210"));
10
+ expect(convertObjectToMap("🍣")).toStrictEqual("🍣");
11
+ expect(convertObjectToMap(undefined)).toBeUndefined();
12
+ expect(convertObjectToMap(null)).toBeNull();
13
+ });
14
+ it("配列を変換すると、同じ内容の配列になる", () => {
15
+ expect(convertObjectToMap([1, 2, 3])).toStrictEqual([1, 2, 3])
16
+ });
17
+ it("オブジェクトを変換すると、Mapになる", () => {
18
+ const result = convertObjectToMap({
19
+ foo: "bar",
20
+ hoge: 31415
21
+ });
22
+ expect(result).toBeInstanceOf(Map);
23
+ expect(result.get("foo")).toEqual("bar");
24
+ expect(result.get("hoge")).toEqual(31415);
25
+ });
26
+ it("オブジェクトを変換したとき、Mapになっても同じ参照は維持される", () => {
27
+ const refA = { foo: { bar: null } };
28
+ const result = convertObjectToMap({
29
+ ref1: refA,
30
+ ref2: refA,
31
+ ref3: { foo: { bar: null } }, // 異なる参照
32
+ });
33
+ expect(result).toBeInstanceOf(Map);
34
+ expect(result.get("ref1") === result.get("ref2")).toBeTruthy();
35
+ expect(result.get("ref1") === result.get("ref3")).toBeFalsy();
36
+ expect(result.get("ref2") === result.get("ref3")).toBeFalsy();
37
+ });
38
+ it("循環参照を変換できる", () => {
39
+ const refA: { next: unknown } = { next: null };
40
+ refA.next = refA;
41
+ const result = convertObjectToMap(refA);
42
+ expect(result).toBeInstanceOf(Map);
43
+ expect(result.get("next") === result).toBeTruthy();
44
+ })
45
+ it("空オブジェクトを変換できる", () => {
46
+ const result = convertObjectToMap({});
47
+ expect(result).toBeInstanceOf(Map);
48
+ expect(result.size).toBe(0);
49
+ })
50
+ it("配列とオブジェクトがネストしていても変換できる", () => {
51
+ const refA = { foo: { bar: null as unknown } };
52
+ const refB = [{
53
+ ref1: refA,
54
+ ref2: refA,
55
+ ref3: { foo: { bar: null } },
56
+ ref4: [{test1: { test2: { test3: {} }}}]
57
+ }, refA] as const;
58
+ refA.foo.bar = refB;
59
+ const result = convertObjectToMap(refB)
60
+ expect(result).toBeInstanceOf(Array);
61
+ expect(result[0]).toBeInstanceOf(Map);
62
+ expect(result[0]).toBeInstanceOf(Map);
63
+ expect(result[0].get("ref1") === result[0].get("ref2")).toBeTruthy();
64
+ expect(result[0].get("ref1") === result[0].get("ref3")).toBeFalsy();
65
+ expect(result[0].get("ref2") === result[0].get("ref3")).toBeFalsy();
66
+ expect(result[0].get("ref1") === result[1]).toBeTruthy();
67
+ const resultRef1 = result[0].get("ref1");
68
+ if (!(resultRef1 instanceof Map)) {
69
+ fail("変換結果の ref1 が Map になっていません!");
70
+ }
71
+ const resultFoo = resultRef1.get("foo");
72
+ if (!(resultFoo instanceof Map)) {
73
+ fail("変換結果の foo が Map になっていません!");
74
+ }
75
+ expect(resultFoo.get("bar") === result).toBeTruthy();
76
+ const resultRef4 = result[0].get("ref4");
77
+ if (!(resultRef4 instanceof Array)) {
78
+ fail("変換結果の ref4 が Map になっていません!");
79
+ }
80
+ const resultRef4_0 = resultRef4[0];
81
+ if (!(resultRef4_0 instanceof Map)) {
82
+ fail("変換結果の ref4[0] が Map になっていません!");
83
+ }
84
+ const maybeEmptyMap = resultRef4_0.get("test1")?.get("test2")?.get("test3");
85
+ if (!(maybeEmptyMap instanceof Map)) {
86
+ fail("変換結果の ref4[0].test1.test2.test3 が Map になっていません!");
87
+ }
88
+ expect(maybeEmptyMap.size).toBe(0);
89
+ })
90
+
91
+ })
@@ -0,0 +1,2 @@
1
+ // symbol は対象外です
2
+ export type Primitive = number | string | boolean | bigint | undefined | null;
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { convertObjectToMap, type ObjectToMap } from "./objectToMap";
2
+ export { convertMapToObject, } from "./mapToObject";
@@ -0,0 +1,58 @@
1
+ import type { Primitive } from "./_common";
2
+
3
+ type SeenRefMap = WeakMap<Map<string, unknown> | Array<unknown>, unknown>;
4
+
5
+ // WWA ではパスワードセーブに利用されるのみであり、雑な型をつけても特に差し支えない。
6
+ // 保守コストを下げる意味でも雑な型を付けるに留める。
7
+ export function convertMapToObject<T extends Primitive>(
8
+ input: T,
9
+ seen?: SeenRefMap
10
+ ): T;
11
+ export function convertMapToObject<T extends ReadonlyArray<unknown>>(
12
+ input: T,
13
+ seen?:SeenRefMap
14
+ ): ReadonlyArray<unknown>;
15
+ export function convertMapToObject<T extends Array<unknown>>(
16
+ input: T,
17
+ seen?:SeenRefMap
18
+ ): Array<unknown>;
19
+ export function convertMapToObject<T extends Map<string, unknown>>(
20
+ input: T,
21
+ seen?:SeenRefMap
22
+ ): Record<string, unknown>;
23
+ export function convertMapToObject(input: unknown, seen = new WeakMap<Map<string, unknown> | Array<unknown>, unknown>()): any {
24
+ if (typeof input === "symbol") {
25
+ throw new TypeError("symbol は変換対象外です")
26
+ }
27
+
28
+ if (input instanceof Map) {
29
+ if (seen.has(input)) {
30
+ return seen.get(input);
31
+ }
32
+ const obj: { [key: string]: unknown } = Object.create(null);
33
+ seen.set(input, obj);
34
+ for (const [key, value] of input.entries()) {
35
+ obj[key] = convertMapToObject(value, seen);
36
+ }
37
+ return obj;
38
+ }
39
+
40
+ if (Array.isArray(input)) {
41
+ if (seen.has(input)) {
42
+ return seen.get(input);
43
+ }
44
+ const arr: unknown[] = [];
45
+ seen.set(input, arr);
46
+ for (const item of input) {
47
+ arr.push(convertMapToObject(item, seen));
48
+ }
49
+ return arr;
50
+ }
51
+
52
+ if (typeof input === 'object' && input !== null) {
53
+ throw new TypeError("Mapおよび配列でないオブジェクトは変換対象外です");
54
+ }
55
+
56
+ // プリミティブ
57
+ return input;
58
+ }
@@ -0,0 +1,69 @@
1
+ import type { Primitive } from "./_common";
2
+
3
+ type SeenRefMap = WeakMap<object, unknown>;
4
+ export type ObjectToMap<T> = UnionToIntersection<
5
+ T extends Primitive
6
+ ? T
7
+ : T extends ReadonlyArray<infer U>
8
+ ? ReadonlyArray<ObjectToMap<U>>
9
+ : T extends Array<infer U>
10
+ ? Array<ObjectToMap<U>>
11
+ : T extends object
12
+ ? Record<never, never> extends T
13
+ ? Map<string, never>
14
+ : {
15
+ [K in keyof T]: Map<K, ObjectToMap<T[K]>>;
16
+ }[keyof T]
17
+ : never
18
+ >;
19
+
20
+ type UnionToIntersection<U> = (U extends any ? (x: U) => void : never) extends (
21
+ x: infer R
22
+ ) => void
23
+ ? R
24
+ : never;
25
+
26
+ export function convertObjectToMap<T extends Primitive>(
27
+ input: T,
28
+ seen?: SeenRefMap
29
+ ): T;
30
+ export function convertObjectToMap<T extends Array<unknown>>(
31
+ input: T,
32
+ seen?: SeenRefMap
33
+ ): T;
34
+ export function convertObjectToMap<T extends Exclude<object, null>>(
35
+ input: T,
36
+ seen?: SeenRefMap
37
+ ): ObjectToMap<T>;
38
+ export function convertObjectToMap(
39
+ input: unknown,
40
+ seen = new WeakMap<object, unknown>()
41
+ ): unknown {
42
+ if (typeof input === "symbol") {
43
+ throw new TypeError("symbol は変換対象外です")
44
+ }
45
+ if (typeof input !== "object" || input === null) {
46
+ return input;
47
+ }
48
+ if (seen.has(input)) {
49
+ return seen.get(input);
50
+ }
51
+ if (Array.isArray(input)) {
52
+ const arr: unknown[] = [];
53
+ seen.set(input, arr);
54
+ for (const item of input) {
55
+ arr.push(convertObjectToMap(item, seen));
56
+ }
57
+ return arr;
58
+ }
59
+
60
+ const map = new Map<string, unknown>();
61
+ seen.set(input, map);
62
+ for (const [key, value] of Object.entries(input)) {
63
+ if (typeof key !== "string") {
64
+ throw new TypeError("文字列以外のキーは変換できません");
65
+ }
66
+ map.set(key, convertObjectToMap(value, seen));
67
+ }
68
+ return map;
69
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "rootDir": "./src",
5
+ "outDir": "./lib",
6
+ "module": "esnext",
7
+ "strict": true,
8
+ "noImplicitAny": true,
9
+ },
10
+ "include": [
11
+ "src/**/*.ts",
12
+ ],
13
+ "exclude": [
14
+ "node_modules"
15
+ ]
16
+ }