@quanxiaoxiao/datav 0.4.0 → 0.5.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/README.md +680 -165
- package/dist/createArrayAccessor.d.ts +2 -0
- package/dist/createArrayAccessor.d.ts.map +1 -0
- package/dist/createArrayAccessor.js +38 -0
- package/dist/createArrayAccessor.js.map +1 -0
- package/dist/createDataAccessor.d.ts +2 -0
- package/dist/createDataAccessor.d.ts.map +1 -0
- package/dist/createDataAccessor.js +23 -0
- package/dist/createDataAccessor.js.map +1 -0
- package/dist/createDataTransformer.d.ts +14 -0
- package/dist/createDataTransformer.d.ts.map +1 -0
- package/dist/createDataTransformer.js +124 -0
- package/dist/createDataTransformer.js.map +1 -0
- package/dist/createPathAccessor.d.ts +2 -0
- package/dist/createPathAccessor.d.ts.map +1 -0
- package/dist/createPathAccessor.js +38 -0
- package/dist/createPathAccessor.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/parseDotPath.d.ts +2 -0
- package/dist/parseDotPath.d.ts.map +1 -0
- package/dist/parseDotPath.js +14 -0
- package/dist/parseDotPath.js.map +1 -0
- package/dist/parseValueByType.d.ts +11 -0
- package/dist/parseValueByType.d.ts.map +1 -0
- package/dist/parseValueByType.js +122 -0
- package/dist/parseValueByType.js.map +1 -0
- package/dist/utils.d.ts +3 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +22 -0
- package/dist/utils.js.map +1 -0
- package/dist/validateExpressSchema.d.ts +7 -0
- package/dist/validateExpressSchema.d.ts.map +1 -0
- package/dist/validateExpressSchema.js +50 -0
- package/dist/validateExpressSchema.js.map +1 -0
- package/package.json +46 -8
- package/src/createArrayAccessor.test.ts +181 -0
- package/src/createArrayAccessor.ts +48 -0
- package/src/createDataAccessor.test.ts +220 -0
- package/src/createDataAccessor.ts +26 -0
- package/src/createDataTransformer.test.ts +847 -0
- package/src/createDataTransformer.ts +173 -0
- package/src/createPathAccessor.test.ts +217 -0
- package/src/createPathAccessor.ts +45 -0
- package/src/index.ts +11 -0
- package/src/parseDotPath.test.ts +132 -0
- package/src/parseDotPath.ts +13 -0
- package/src/parseValueByType.test.ts +342 -0
- package/src/parseValueByType.ts +165 -0
- package/src/utils.test.ts +85 -0
- package/src/utils.ts +22 -0
- package/src/validateExpressSchema.test.ts +295 -0
- package/src/validateExpressSchema.ts +59 -0
- package/.editorconfig +0 -13
- package/eslint.config.mjs +0 -89
- package/src/checkout.mjs +0 -131
- package/src/checkout.test.mjs +0 -144
- package/src/index.mjs +0 -7
- package/src/select/check.mjs +0 -63
- package/src/select/check.test.mjs +0 -76
- package/src/select/index.mjs +0 -117
- package/src/select/index.test.mjs +0 -1145
package/package.json
CHANGED
|
@@ -1,20 +1,60 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quanxiaoxiao/datav",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
|
+
"description": "Data transformation utility with type conversion, path access, and schema-driven data transformation",
|
|
4
5
|
"type": "module",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"data",
|
|
8
|
+
"transform",
|
|
9
|
+
"schema",
|
|
10
|
+
"typescript",
|
|
11
|
+
"type-conversion",
|
|
12
|
+
"util"
|
|
13
|
+
],
|
|
14
|
+
"author": "quanxiaoxiao",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "https://github.com/quanxiaoxiao/datav.git"
|
|
19
|
+
},
|
|
20
|
+
"bugs": {
|
|
21
|
+
"url": "https://github.com/quanxiaoxiao/datav/issues"
|
|
22
|
+
},
|
|
23
|
+
"homepage": "https://github.com/quanxiaoxiao/datav#readme",
|
|
24
|
+
"files": [
|
|
25
|
+
"src",
|
|
26
|
+
"dist",
|
|
27
|
+
"package.json",
|
|
28
|
+
"README.md"
|
|
29
|
+
],
|
|
30
|
+
"sideEffects": false,
|
|
5
31
|
"scripts": {
|
|
6
|
-
"test": "node --test"
|
|
32
|
+
"test": "TSX_TSCONFIG=tsconfig.test.json node --import tsx --test 'src/**/*.test.ts'",
|
|
33
|
+
"build": "tsc && rm -rf dist/*.test.* dist/**/*.test.*",
|
|
34
|
+
"check": "tsc --noEmit",
|
|
35
|
+
"lint": "eslint .",
|
|
36
|
+
"format": "prettier --write ."
|
|
7
37
|
},
|
|
8
|
-
"main": "./
|
|
38
|
+
"main": "./dist/index.js",
|
|
39
|
+
"types": "./dist/index.d.ts",
|
|
40
|
+
"module": "./dist/index.js",
|
|
9
41
|
"exports": {
|
|
10
|
-
".":
|
|
42
|
+
".": {
|
|
43
|
+
"types": "./dist/index.d.ts",
|
|
44
|
+
"import": "./dist/index.js",
|
|
45
|
+
"default": "./dist/index.js"
|
|
46
|
+
},
|
|
11
47
|
"./package.json": "./package.json"
|
|
12
48
|
},
|
|
13
49
|
"devDependencies": {
|
|
14
50
|
"@eslint/js": "^9.22.0",
|
|
51
|
+
"@types/node": "^25.0.8",
|
|
15
52
|
"eslint": "^9.22.0",
|
|
16
53
|
"eslint-plugin-simple-import-sort": "^12.1.1",
|
|
17
|
-
"globals": "^
|
|
54
|
+
"globals": "^17.0.0",
|
|
55
|
+
"tsx": "^4.21.0",
|
|
56
|
+
"typescript": "^5.9.3",
|
|
57
|
+
"typescript-eslint": "^8.53.0"
|
|
18
58
|
},
|
|
19
59
|
"publishConfig": {
|
|
20
60
|
"registry": "https://registry.npmjs.org"
|
|
@@ -23,8 +63,6 @@
|
|
|
23
63
|
"node": ">= 20.0.0"
|
|
24
64
|
},
|
|
25
65
|
"dependencies": {
|
|
26
|
-
"
|
|
27
|
-
"ajv": "^8.12.0",
|
|
28
|
-
"lodash": "^4.17.21"
|
|
66
|
+
"ajv": "^8.12.0"
|
|
29
67
|
}
|
|
30
68
|
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import * as assert from 'node:assert';
|
|
2
|
+
import { describe, it } from 'node:test';
|
|
3
|
+
|
|
4
|
+
import { createArrayAccessor } from './createArrayAccessor.js';
|
|
5
|
+
|
|
6
|
+
describe('createArrayAccessor', () => {
|
|
7
|
+
const testArray = ['a', 'b', 'c', 'd', 'e'];
|
|
8
|
+
|
|
9
|
+
describe('数字索引访问', () => {
|
|
10
|
+
it('应该返回正确的数组元素 - 正数索引', () => {
|
|
11
|
+
const accessor = createArrayAccessor(0);
|
|
12
|
+
assert.strictEqual(accessor(testArray), 'a');
|
|
13
|
+
|
|
14
|
+
const accessor2 = createArrayAccessor(2);
|
|
15
|
+
assert.strictEqual(accessor2(testArray), 'c');
|
|
16
|
+
|
|
17
|
+
const accessor4 = createArrayAccessor(4);
|
|
18
|
+
assert.strictEqual(accessor4(testArray), 'e');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('应该支持负数索引', () => {
|
|
22
|
+
const accessor = createArrayAccessor(-1);
|
|
23
|
+
assert.strictEqual(accessor(testArray), 'e');
|
|
24
|
+
|
|
25
|
+
const accessor2 = createArrayAccessor(-2);
|
|
26
|
+
assert.strictEqual(accessor2(testArray), 'd');
|
|
27
|
+
|
|
28
|
+
const accessor5 = createArrayAccessor(-5);
|
|
29
|
+
assert.strictEqual(accessor5(testArray), 'a');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('应该处理索引越界的情况 - 正数', () => {
|
|
33
|
+
const accessor = createArrayAccessor(10);
|
|
34
|
+
assert.strictEqual(accessor(testArray), null);
|
|
35
|
+
|
|
36
|
+
const accessor2 = createArrayAccessor(5);
|
|
37
|
+
assert.strictEqual(accessor2(testArray), null);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('应该处理索引越界的情况 - 负数', () => {
|
|
41
|
+
const accessor = createArrayAccessor(-10);
|
|
42
|
+
assert.strictEqual(accessor(testArray), null);
|
|
43
|
+
|
|
44
|
+
const accessor2 = createArrayAccessor(-6);
|
|
45
|
+
assert.strictEqual(accessor2(testArray), null);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('应该拒绝浮点数索引', () => {
|
|
49
|
+
const accessor = createArrayAccessor(1.5);
|
|
50
|
+
assert.strictEqual(accessor(testArray), null);
|
|
51
|
+
|
|
52
|
+
const accessor2 = createArrayAccessor(-2.3);
|
|
53
|
+
assert.strictEqual(accessor2(testArray), null);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe('字符串索引访问', () => {
|
|
58
|
+
it('应该处理 "length" 特殊属性', () => {
|
|
59
|
+
const accessor = createArrayAccessor('length');
|
|
60
|
+
assert.strictEqual(accessor(testArray), 5);
|
|
61
|
+
assert.strictEqual(accessor([]), 0);
|
|
62
|
+
assert.strictEqual(accessor([1, 2, 3]), 3);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('应该将有效的数字字符串转换为索引', () => {
|
|
66
|
+
const accessor = createArrayAccessor('0');
|
|
67
|
+
assert.strictEqual(accessor(testArray), 'a');
|
|
68
|
+
|
|
69
|
+
const accessor2 = createArrayAccessor('3');
|
|
70
|
+
assert.strictEqual(accessor2(testArray), 'd');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('应该支持负数字符串索引', () => {
|
|
74
|
+
const accessor = createArrayAccessor('-1');
|
|
75
|
+
assert.strictEqual(accessor(testArray), 'e');
|
|
76
|
+
|
|
77
|
+
const accessor2 = createArrayAccessor('-3');
|
|
78
|
+
assert.strictEqual(accessor2(testArray), 'c');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('应该拒绝无效的字符串索引', () => {
|
|
82
|
+
const accessor = createArrayAccessor('abc');
|
|
83
|
+
assert.strictEqual(accessor(testArray), null);
|
|
84
|
+
|
|
85
|
+
const accessor2 = createArrayAccessor('1.5');
|
|
86
|
+
assert.strictEqual(accessor2(testArray), null);
|
|
87
|
+
|
|
88
|
+
const accessor3 = createArrayAccessor('01'); // 前导零
|
|
89
|
+
assert.strictEqual(accessor3(testArray), null);
|
|
90
|
+
|
|
91
|
+
const accessor4 = createArrayAccessor('2a');
|
|
92
|
+
assert.strictEqual(accessor4(testArray), null);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe('边界情况', () => {
|
|
97
|
+
it('应该处理空数组', () => {
|
|
98
|
+
const accessor = createArrayAccessor(0);
|
|
99
|
+
assert.strictEqual(accessor([]), null);
|
|
100
|
+
|
|
101
|
+
const accessor2 = createArrayAccessor(-1);
|
|
102
|
+
assert.strictEqual(accessor2([]), null);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('应该处理单元素数组', () => {
|
|
106
|
+
const singleArray = ['only'];
|
|
107
|
+
|
|
108
|
+
const accessor0 = createArrayAccessor(0);
|
|
109
|
+
assert.strictEqual(accessor0(singleArray), 'only');
|
|
110
|
+
|
|
111
|
+
const accessor1 = createArrayAccessor(-1);
|
|
112
|
+
assert.strictEqual(accessor1(singleArray), 'only');
|
|
113
|
+
|
|
114
|
+
const accessor2 = createArrayAccessor(1);
|
|
115
|
+
assert.strictEqual(accessor2(singleArray), null);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('应该拒绝无效的索引类型', () => {
|
|
119
|
+
const accessor = createArrayAccessor(null as unknown);
|
|
120
|
+
assert.strictEqual(accessor(testArray), null);
|
|
121
|
+
|
|
122
|
+
const accessor2 = createArrayAccessor(undefined as unknown);
|
|
123
|
+
assert.strictEqual(accessor2(testArray), null);
|
|
124
|
+
|
|
125
|
+
const accessor3 = createArrayAccessor({} as unknown);
|
|
126
|
+
assert.strictEqual(accessor3(testArray), null);
|
|
127
|
+
|
|
128
|
+
const accessor4 = createArrayAccessor([] as unknown);
|
|
129
|
+
assert.strictEqual(accessor4(testArray), null);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('应该处理包含各种类型元素的数组', () => {
|
|
133
|
+
const mixedArray = [null, undefined, 0, '', false, { key: 'value' }];
|
|
134
|
+
|
|
135
|
+
const accessor0 = createArrayAccessor(0);
|
|
136
|
+
assert.strictEqual(accessor0(mixedArray), null);
|
|
137
|
+
|
|
138
|
+
const accessor1 = createArrayAccessor(1);
|
|
139
|
+
assert.strictEqual(accessor1(mixedArray), undefined);
|
|
140
|
+
|
|
141
|
+
const accessor5 = createArrayAccessor(5);
|
|
142
|
+
assert.deepStrictEqual(accessor5(mixedArray), { key: 'value' });
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
describe('函数复用性', () => {
|
|
147
|
+
it('创建的访问器应该可以在多个数组上复用', () => {
|
|
148
|
+
const accessor = createArrayAccessor(1);
|
|
149
|
+
|
|
150
|
+
assert.strictEqual(accessor(['a', 'b', 'c']), 'b');
|
|
151
|
+
assert.strictEqual(accessor([10, 20, 30]), 20);
|
|
152
|
+
assert.strictEqual(accessor(['x']), null);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('应该是纯函数,不改变原数组', () => {
|
|
156
|
+
const originalArray = [1, 2, 3];
|
|
157
|
+
const accessor = createArrayAccessor(0);
|
|
158
|
+
|
|
159
|
+
accessor(originalArray);
|
|
160
|
+
assert.deepStrictEqual(originalArray, [1, 2, 3]);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
describe('特殊数字情况', () => {
|
|
165
|
+
it('应该正确处理 0 索引', () => {
|
|
166
|
+
const accessor = createArrayAccessor(0);
|
|
167
|
+
assert.strictEqual(accessor(testArray), 'a');
|
|
168
|
+
assert.strictEqual(accessor(['only']), 'only');
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('应该正确处理 -0', () => {
|
|
172
|
+
const accessor = createArrayAccessor(-0);
|
|
173
|
+
assert.strictEqual(accessor(testArray), 'a');
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('应该处理字符串 "0"', () => {
|
|
177
|
+
const accessor = createArrayAccessor('0');
|
|
178
|
+
assert.strictEqual(accessor(testArray), 'a');
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const getArrayElementAt = (index: number, array: unknown[]): unknown => {
|
|
2
|
+
const length = array.length;
|
|
3
|
+
|
|
4
|
+
if (length === 0) {
|
|
5
|
+
return null;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
if (index < 0) {
|
|
9
|
+
const actualIndex = length + index;
|
|
10
|
+
if (actualIndex < 0) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
return array[actualIndex];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (index >= length) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return array[index];
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export function createArrayAccessor(index: string | number): (array: unknown[]) => unknown {
|
|
24
|
+
const indexType = typeof index;
|
|
25
|
+
|
|
26
|
+
if (indexType !== 'string' && indexType !== 'number') {
|
|
27
|
+
return () => null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (indexType === 'string') {
|
|
31
|
+
if (index === 'length') {
|
|
32
|
+
return (array: unknown[]) => array.length;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const numericIndex = parseInt(index as string, 10);
|
|
36
|
+
if (Number.isNaN(numericIndex) || numericIndex.toString() !== index) {
|
|
37
|
+
return () => null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return (array: unknown[]) => getArrayElementAt(numericIndex, array);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!Number.isInteger(index as number)) {
|
|
44
|
+
return () => null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return (array: unknown[]) => getArrayElementAt(index as number, array);
|
|
48
|
+
}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import * as assert from 'node:assert';
|
|
2
|
+
import { describe, it } from 'node:test';
|
|
3
|
+
|
|
4
|
+
import { createDataAccessor } from './createDataAccessor.js';
|
|
5
|
+
import { createArrayAccessor } from './createArrayAccessor.js';
|
|
6
|
+
import { createPathAccessor } from './createPathAccessor.js';
|
|
7
|
+
import { parseDotPath } from './parseDotPath.js';
|
|
8
|
+
|
|
9
|
+
describe('createDataAccessor', () => {
|
|
10
|
+
describe('当 pathname 为 null 或 undefined 时', () => {
|
|
11
|
+
it('应该返回一个始终返回 null 的函数', () => {
|
|
12
|
+
const accessor = createDataAccessor(null);
|
|
13
|
+
assert.strictEqual(accessor({ foo: 'bar' }), null);
|
|
14
|
+
assert.strictEqual(accessor([1, 2, 3]), null);
|
|
15
|
+
assert.strictEqual(accessor('test'), null);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('应该处理 undefined', () => {
|
|
19
|
+
const accessor = createDataAccessor(undefined as unknown as string | number | null);
|
|
20
|
+
assert.strictEqual(accessor({ foo: 'bar' }), null);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe('当 pathname 为数字时', () => {
|
|
25
|
+
it('应该为数组创建索引访问器', () => {
|
|
26
|
+
const accessor = createDataAccessor(2);
|
|
27
|
+
const testArray = ['a', 'b', 'c'];
|
|
28
|
+
assert.strictEqual(accessor(testArray), 'c');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('当数据不是数组时应该返回 null', () => {
|
|
32
|
+
const accessor = createDataAccessor(0);
|
|
33
|
+
assert.strictEqual(accessor({ foo: 'bar' }), null);
|
|
34
|
+
assert.strictEqual(accessor('string'), null);
|
|
35
|
+
assert.strictEqual(accessor(123), null);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('应该处理负数索引', () => {
|
|
39
|
+
const accessor = createDataAccessor(-1);
|
|
40
|
+
const testArray = [1, 2, 3];
|
|
41
|
+
assert.strictEqual(accessor(testArray), 3);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe('当 pathname 为字符串时', () => {
|
|
46
|
+
it('应该解析点路径并创建路径访问器', () => {
|
|
47
|
+
const accessor = createDataAccessor('user.name');
|
|
48
|
+
const result = accessor({ user: { name: 'John' } });
|
|
49
|
+
assert.strictEqual(result, 'John');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('应该处理简单的属性路径', () => {
|
|
53
|
+
const accessor = createDataAccessor('name');
|
|
54
|
+
const result = accessor({ name: 'Alice' });
|
|
55
|
+
assert.strictEqual(result, 'Alice');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('应该处理深层嵌套路径', () => {
|
|
59
|
+
const accessor = createDataAccessor('a.b.c.d');
|
|
60
|
+
const result = accessor({ a: { b: { c: { d: 'deep value' } } } });
|
|
61
|
+
assert.strictEqual(result, 'deep value');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('应该返回原数据', () => {
|
|
65
|
+
const accessor = createDataAccessor('');
|
|
66
|
+
const input = { name: 'John' };
|
|
67
|
+
assert.strictEqual(accessor(input), input);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('应该处理数组索引', () => {
|
|
71
|
+
const accessor = createDataAccessor('0');
|
|
72
|
+
const result = accessor({ 0: 'first', 1: 'second' });
|
|
73
|
+
assert.strictEqual(result, 'first');
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe('当 pathname 为其他类型时', () => {
|
|
78
|
+
it('应该返回一个始终返回 null 的函数 (boolean)', () => {
|
|
79
|
+
const accessor = createDataAccessor(true as unknown as string | number | null);
|
|
80
|
+
assert.strictEqual(accessor({ foo: 'bar' }), null);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('应该返回一个始终返回 null 的函数 (object)', () => {
|
|
84
|
+
const accessor = createDataAccessor({} as unknown as string | number | null);
|
|
85
|
+
assert.strictEqual(accessor({ foo: 'bar' }), null);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('应该返回一个始终返回 null 的函数 (array)', () => {
|
|
89
|
+
const accessor = createDataAccessor([] as unknown as string | number | null);
|
|
90
|
+
assert.strictEqual(accessor({ foo: 'bar' }), null);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe('访问器函数的复用', () => {
|
|
95
|
+
it('同一个访问器应该可以多次调用', () => {
|
|
96
|
+
const accessor = createDataAccessor('value');
|
|
97
|
+
|
|
98
|
+
accessor({ value: 1 });
|
|
99
|
+
accessor({ value: 2 });
|
|
100
|
+
const result = accessor({ value: 3 });
|
|
101
|
+
|
|
102
|
+
assert.strictEqual(result, 3);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('不同的数据类型应该正确处理', () => {
|
|
106
|
+
const accessor = createDataAccessor(0);
|
|
107
|
+
|
|
108
|
+
const result1 = accessor([1, 2, 3]);
|
|
109
|
+
const result2 = accessor({ 0: 'not array' });
|
|
110
|
+
|
|
111
|
+
assert.strictEqual(result1, 1);
|
|
112
|
+
assert.strictEqual(result2, null);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe('createArrayAccessor', () => {
|
|
118
|
+
it('应该返回数组指定索引的元素', () => {
|
|
119
|
+
const accessor = createArrayAccessor(0);
|
|
120
|
+
assert.strictEqual(accessor(['a', 'b', 'c']), 'a');
|
|
121
|
+
|
|
122
|
+
const accessor2 = createArrayAccessor(2);
|
|
123
|
+
assert.strictEqual(accessor2(['a', 'b', 'c']), 'c');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('应该处理负数索引', () => {
|
|
127
|
+
const accessor = createArrayAccessor(-1);
|
|
128
|
+
assert.strictEqual(accessor(['a', 'b', 'c']), 'c');
|
|
129
|
+
|
|
130
|
+
const accessor2 = createArrayAccessor(-2);
|
|
131
|
+
assert.strictEqual(accessor2(['a', 'b', 'c']), 'b');
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('应该处理越界索引', () => {
|
|
135
|
+
const accessor = createArrayAccessor(10);
|
|
136
|
+
assert.strictEqual(accessor(['a', 'b', 'c']), null);
|
|
137
|
+
|
|
138
|
+
const accessor2 = createArrayAccessor(-10);
|
|
139
|
+
assert.strictEqual(accessor2(['a', 'b', 'c']), null);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('应该处理空数组', () => {
|
|
143
|
+
const accessor = createArrayAccessor(0);
|
|
144
|
+
assert.strictEqual(accessor([]), null);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('应该返回数组长度', () => {
|
|
148
|
+
const accessor = createArrayAccessor('length');
|
|
149
|
+
assert.strictEqual(accessor(['a', 'b', 'c']), 3);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('应该处理无效的字符串索引', () => {
|
|
153
|
+
const accessor = createArrayAccessor('abc');
|
|
154
|
+
assert.strictEqual(accessor(['a', 'b', 'c']), null);
|
|
155
|
+
|
|
156
|
+
const accessor2 = createArrayAccessor('1.5');
|
|
157
|
+
assert.strictEqual(accessor2(['a', 'b', 'c']), null);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('应该拒绝浮点数索引', () => {
|
|
161
|
+
const accessor = createArrayAccessor(1.5);
|
|
162
|
+
assert.strictEqual(accessor(['a', 'b', 'c']), null);
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
describe('createPathAccessor', () => {
|
|
167
|
+
it('应该访问单个键', () => {
|
|
168
|
+
const accessor = createPathAccessor(['name']);
|
|
169
|
+
assert.strictEqual(accessor({ name: 'John' }), 'John');
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('应该访问嵌套路径', () => {
|
|
173
|
+
const accessor = createPathAccessor(['user', 'profile', 'name']);
|
|
174
|
+
assert.strictEqual(accessor({ user: { profile: { name: 'John' } } }), 'John');
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('应该处理不存在的路径', () => {
|
|
178
|
+
const accessor = createPathAccessor(['nonexistent']);
|
|
179
|
+
assert.strictEqual(accessor({ name: 'John' }), null);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('应该返回原数据', () => {
|
|
183
|
+
const accessor = createPathAccessor([]);
|
|
184
|
+
const input = { name: 'John' };
|
|
185
|
+
assert.strictEqual(accessor(input), input);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('应该处理数组访问', () => {
|
|
189
|
+
const accessor = createPathAccessor(['items', '0', 'name']);
|
|
190
|
+
assert.strictEqual(accessor({ items: [{ name: 'first' }, { name: 'second' }] }), 'first');
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
describe('parseDotPath', () => {
|
|
195
|
+
it('应该解析简单路径', () => {
|
|
196
|
+
assert.deepStrictEqual(parseDotPath('user.name'), ['user', 'name']);
|
|
197
|
+
assert.deepStrictEqual(parseDotPath('a.b.c'), ['a', 'b', 'c']);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('应该处理前导点', () => {
|
|
201
|
+
assert.deepStrictEqual(parseDotPath('.user.name'), ['user', 'name']);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('应该处理空字符串', () => {
|
|
205
|
+
assert.deepStrictEqual(parseDotPath(''), []);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('应该处理单个段', () => {
|
|
209
|
+
assert.deepStrictEqual(parseDotPath('name'), ['name']);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('应该处理转义点号', () => {
|
|
213
|
+
assert.deepStrictEqual(parseDotPath('user\\.name'), ['user.name']);
|
|
214
|
+
assert.deepStrictEqual(parseDotPath('a\\.b.c'), ['a.b', 'c']);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('应该处理前导点后无内容', () => {
|
|
218
|
+
assert.deepStrictEqual(parseDotPath('.'), []);
|
|
219
|
+
});
|
|
220
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { createArrayAccessor } from './createArrayAccessor.js';
|
|
2
|
+
import { createPathAccessor } from './createPathAccessor.js';
|
|
3
|
+
import { parseDotPath } from './parseDotPath.js';
|
|
4
|
+
|
|
5
|
+
export function createDataAccessor(pathname: string | number | null): (data: unknown) => unknown {
|
|
6
|
+
if (pathname == null) {
|
|
7
|
+
return () => null;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
if (typeof pathname === 'number') {
|
|
11
|
+
const arrayAccessor = createArrayAccessor(pathname);
|
|
12
|
+
return (data: unknown) => {
|
|
13
|
+
if (!Array.isArray(data)) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
return arrayAccessor(data);
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (typeof pathname === 'string') {
|
|
21
|
+
const parsedPath = parseDotPath(pathname);
|
|
22
|
+
return createPathAccessor(parsedPath);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return () => null;
|
|
26
|
+
}
|