@quanxiaoxiao/datav 0.3.2 → 0.5.0

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.
Files changed (64) hide show
  1. package/README.md +680 -165
  2. package/dist/createArrayAccessor.d.ts +2 -0
  3. package/dist/createArrayAccessor.d.ts.map +1 -0
  4. package/dist/createArrayAccessor.js +38 -0
  5. package/dist/createArrayAccessor.js.map +1 -0
  6. package/dist/createDataAccessor.d.ts +2 -0
  7. package/dist/createDataAccessor.d.ts.map +1 -0
  8. package/dist/createDataAccessor.js +23 -0
  9. package/dist/createDataAccessor.js.map +1 -0
  10. package/dist/createDataTransformer.d.ts +14 -0
  11. package/dist/createDataTransformer.d.ts.map +1 -0
  12. package/dist/createDataTransformer.js +124 -0
  13. package/dist/createDataTransformer.js.map +1 -0
  14. package/dist/createPathAccessor.d.ts +2 -0
  15. package/dist/createPathAccessor.d.ts.map +1 -0
  16. package/dist/createPathAccessor.js +38 -0
  17. package/dist/createPathAccessor.js.map +1 -0
  18. package/dist/index.d.ts +5 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +5 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/parseDotPath.d.ts +2 -0
  23. package/dist/parseDotPath.d.ts.map +1 -0
  24. package/dist/parseDotPath.js +14 -0
  25. package/dist/parseDotPath.js.map +1 -0
  26. package/dist/parseValueByType.d.ts +11 -0
  27. package/dist/parseValueByType.d.ts.map +1 -0
  28. package/dist/parseValueByType.js +122 -0
  29. package/dist/parseValueByType.js.map +1 -0
  30. package/dist/utils.d.ts +3 -0
  31. package/dist/utils.d.ts.map +1 -0
  32. package/dist/utils.js +22 -0
  33. package/dist/utils.js.map +1 -0
  34. package/dist/validateExpressSchema.d.ts +7 -0
  35. package/dist/validateExpressSchema.d.ts.map +1 -0
  36. package/dist/validateExpressSchema.js +53 -0
  37. package/dist/validateExpressSchema.js.map +1 -0
  38. package/package.json +50 -10
  39. package/src/createArrayAccessor.test.ts +181 -0
  40. package/src/createArrayAccessor.ts +48 -0
  41. package/src/createDataAccessor.test.ts +220 -0
  42. package/src/createDataAccessor.ts +26 -0
  43. package/src/createDataTransformer.test.ts +847 -0
  44. package/src/createDataTransformer.ts +173 -0
  45. package/src/createPathAccessor.test.ts +217 -0
  46. package/src/createPathAccessor.ts +45 -0
  47. package/src/index.ts +11 -0
  48. package/src/parseDotPath.test.ts +132 -0
  49. package/src/parseDotPath.ts +13 -0
  50. package/src/parseValueByType.test.ts +342 -0
  51. package/src/parseValueByType.ts +165 -0
  52. package/src/utils.test.ts +85 -0
  53. package/src/utils.ts +22 -0
  54. package/src/validateExpressSchema.test.ts +295 -0
  55. package/src/validateExpressSchema.ts +62 -0
  56. package/.editorconfig +0 -13
  57. package/eslint.config.mjs +0 -40
  58. package/src/checkout.mjs +0 -134
  59. package/src/checkout.test.mjs +0 -143
  60. package/src/index.mjs +0 -7
  61. package/src/select/check.mjs +0 -63
  62. package/src/select/check.test.mjs +0 -75
  63. package/src/select/index.mjs +0 -116
  64. package/src/select/index.test.mjs +0 -1120
package/README.md CHANGED
@@ -1,190 +1,705 @@
1
- 类似mongo,aggregate的$project,借助表达式,可以自由地选择性地保留或过滤输入数据中的某些字段,并可以按需对数据进行转换处理。
1
+ # @quanxiaoxiao/datav
2
2
 
3
- ## Install
3
+ [![npm version](https://img.shields.io/npm/v/@quanxiaoxiao/datav.svg)](https://www.npmjs.com/package/@quanxiaoxiao/datav)
4
+ [![npm license](https://img.shields.io/npm/l/@quanxiaoxiao/datav.svg)](https://opensource.org/licenses/MIT)
5
+ [![node version](https://img.shields.io/node/v/@quanxiaoxiao/datav.svg)](https://nodejs.org)
4
6
 
5
- ```shell
7
+ 数据转换工具库,支持类型转换、数据路径访问和 schema 驱动的数据转换。
8
+
9
+ ## 特性
10
+
11
+ - **类型安全** - 完整的 TypeScript 类型支持
12
+ - **Schema 驱动** - 通过声明式 schema 定义转换规则
13
+ - **路径访问** - 支持点分路径访问嵌套数据
14
+ - **根路径引用** - 使用 `$` 从根数据访问
15
+ - **轻量级** - 零额外依赖
16
+
17
+ ## 安装
18
+
19
+ ```bash
6
20
  npm install @quanxiaoxiao/datav
7
21
  ```
8
22
 
9
- ## Quick Start
23
+ ## 快速开始
24
+
25
+ ```typescript
26
+ import { createDataTransformer } from '@quanxiaoxiao/datav';
27
+
28
+ // 定义转换规则
29
+ const schema = {
30
+ type: 'object',
31
+ properties: {
32
+ name: { type: 'string' },
33
+ age: { type: 'integer' },
34
+ email: { type: 'string' }
35
+ }
36
+ };
37
+
38
+ // 创建转换器
39
+ const transform = createDataTransformer(schema);
40
+
41
+ // 转换数据
42
+ const input = {
43
+ name: '张三',
44
+ age: '25',
45
+ email: 'zhangsan@example.com',
46
+ phone: '13800138000', // 忽略此字段
47
+ address: '北京市' // 忽略此字段
48
+ };
49
+
50
+ const result = transform(input);
51
+ console.log(result);
52
+ // 输出: { name: '张三', age: 25, email: 'zhangsan@example.com' }
53
+ ```
54
+
55
+ ## 核心功能
10
56
 
11
- ```javascript
12
- import { select } from '@quanxiaoxiao/datav';
57
+ ### createDataTransformer
13
58
 
14
- select({ type: 'number' })('33.3'); // 33.3
15
- select({ type: 'integer' })('33.3'); // 33
16
- select({ type: 'boolean' })('true'); // true
17
- select({ type: 'boolean' })('false'); // false
59
+ 根据 schema 定义将数据从一种格式转换为另一种格式。
18
60
 
19
- select({
61
+ ```typescript
62
+ import { createDataTransformer } from '@quanxiaoxiao/datav';
63
+ ```
64
+
65
+ #### 基础类型转换
66
+
67
+ ```typescript
68
+ const transform = createDataTransformer({ type: 'string' });
69
+ transform(123); // '123'
70
+ transform(true); // 'true'
71
+ transform(null); // null
72
+ ```
73
+
74
+ | 类型 | 输入示例 | 输出 | 说明 |
75
+ |------|----------|------|------|
76
+ | `string` | `123` | `'123'` | 转换为字符串 |
77
+ | `number` | `'123.45'` | `123.45` | 转换为数字(保留小数) |
78
+ | `integer` | `'123.45'` | `123` | 转换为整数(截断小数) |
79
+ | `boolean` | `'true'` | `true` | 转换为布尔值 |
80
+
81
+ #### 对象转换
82
+
83
+ 提取并转换对象的指定字段:
84
+
85
+ ```typescript
86
+ const schema = {
20
87
  type: 'object',
21
88
  properties: {
22
- name: {
23
- type: 'string',
24
- },
25
- age: {
26
- type: 'integer',
27
- },
28
- },
29
- })({ name: 'quan', age: '22.2', foo: 'bar' }); // { name: "quan", age: 22 }
30
-
31
- select(['age', { type: 'integer' }])({ age: '33.3' }); // 33
32
- ```
33
-
34
- ## Express
35
-
36
- ```json
37
- {
38
- "type": "object",
39
- "anyOf": [
40
- {
41
- "properties": {
42
- "type": {
43
- "enum": [
44
- "object"
45
- ]
46
- },
47
- "properties": {
48
- "type": "object"
49
- }
50
- },
51
- "required": [
52
- "type",
53
- "properties"
54
- ]
55
- },
56
- {
57
- "properties": {
58
- "type": {
59
- "enum": [
60
- "array"
61
- ]
62
- },
63
- "properties": {
64
- "anyOf": [
65
- {
66
- "type": "object"
67
- },
68
- {
69
- "type": "array",
70
- "items": [
71
- {
72
- "type": "string"
73
- },
74
- {
75
- "type": "object"
76
- }
77
- ],
78
- "additionalItems": false,
79
- "minItems": 2,
80
- "maxItems": 2
81
- }
82
- ]
83
- }
84
- },
85
- "required": [
86
- "type",
87
- "properties"
88
- ]
89
- },
90
- {
91
- "properties": {
92
- "type": {
93
- "enum": [
94
- "string",
95
- "number",
96
- "boolean",
97
- "integer"
98
- ]
99
- }
100
- },
101
- "required": [
102
- "type"
103
- ]
89
+ name: { type: 'string' },
90
+ age: { type: 'integer' },
91
+ score: { type: 'number' }
92
+ }
93
+ };
94
+
95
+ const transform = createDataTransformer(schema);
96
+
97
+ transform({
98
+ name: '李四',
99
+ age: '28.7',
100
+ score: '95.5',
101
+ email: 'lisi@example.com',
102
+ phone: '13900139000'
103
+ });
104
+ // { name: '李四', age: 28, score: 95.5 }
105
+ ```
106
+
107
+ #### 嵌套对象转换
108
+
109
+ ```typescript
110
+ const schema = {
111
+ type: 'object',
112
+ properties: {
113
+ user: {
114
+ type: 'object',
115
+ properties: {
116
+ name: { type: 'string' },
117
+ profile: {
118
+ type: 'object',
119
+ properties: {
120
+ age: { type: 'integer' },
121
+ city: { type: 'string' }
122
+ }
104
123
  }
105
- ]
106
- }
124
+ }
125
+ }
126
+ }
127
+ };
128
+
129
+ const transform = createDataTransformer(schema);
130
+
131
+ transform({
132
+ user: {
133
+ name: '王五',
134
+ profile: {
135
+ age: '30',
136
+ city: '上海市',
137
+ phone: '13700137000'
138
+ }
139
+ }
140
+ });
141
+ // { user: { name: '王五', profile: { age: 30, city: '上海市' } } }
142
+ ```
143
+
144
+ #### 路径访问
145
+
146
+ 使用 `[路径, schema]` 语法访问嵌套数据:
147
+
148
+ ```typescript
149
+ // 访问深层嵌套字段
150
+ const schema = ['user.profile.age', { type: 'integer' }];
151
+ const transform = createDataTransformer(schema);
152
+
153
+ transform({ user: { profile: { age: '25' } } }); // 25
154
+ transform({ user: { profile: { age: null } } }); // null
155
+ transform({ user: { name: 'test' } }); // null (路径不存在)
156
+ ```
157
+
158
+ #### 根路径引用 (`$`)
159
+
160
+ 使用 `$` 从根数据开始访问,常用于在数组转换中引用根数据:
161
+
162
+ ```typescript
163
+ // 从根路径提取数据填充数组
164
+ const schema = {
165
+ type: 'array',
166
+ properties: ['$items', { type: 'string' }]
167
+ };
168
+ const transform = createDataTransformer(schema);
169
+
170
+ transform({ items: ['a', 'b', 'c'], other: 'data' });
171
+ // ['a', 'b', 'c']
172
+ ```
173
+
174
+ ```typescript
175
+ // 组合使用路径和根路径
176
+ const schema = {
177
+ type: 'object',
178
+ properties: {
179
+ userId: ['.$id', { type: 'integer' }],
180
+ items: {
181
+ type: 'array',
182
+ properties: {
183
+ name: { type: 'string' },
184
+ userName: ['$userName', { type: 'string' }]
185
+ }
186
+ }
187
+ }
188
+ };
189
+
190
+ const transform = createDataTransformer(schema);
191
+
192
+ transform({
193
+ id: '1001',
194
+ userName: '张三',
195
+ items: [
196
+ { name: '商品A' },
197
+ { name: '商品B' }
198
+ ]
199
+ });
200
+ // {
201
+ // userId: 1001,
202
+ // items: [
203
+ // { name: '商品A', userName: '张三' },
204
+ // { name: '商品B', userName: '张三' }
205
+ // ]
206
+ // }
207
+ ```
208
+
209
+ #### 数组转换
210
+
211
+ **元组形式** - 转换每个数组元素:
212
+
213
+ ```typescript
214
+ // 转换字符串数组为整数数组
215
+ const schema = {
216
+ type: 'array',
217
+ properties: ['.', { type: 'integer' }]
218
+ };
219
+ const transform = createDataTransformer(schema);
220
+
221
+ transform(['1.1', '2.9', '3']); // [1, 2, 3]
222
+ transform(['10', '20', '30']); // [10, 20, 30]
223
+ ```
224
+
225
+ **对象形式** - 提取数组中对象的指定字段:
226
+
227
+ ```typescript
228
+ const schema = {
229
+ type: 'array',
230
+ properties: { name: { type: 'string' }, age: { type: 'integer' } }
231
+ };
232
+ const transform = createDataTransformer(schema);
233
+
234
+ transform([
235
+ { name: '张三', age: '25', email: 'a@a.com' },
236
+ { name: '李四', age: '30', email: 'b@b.com' }
237
+ ]);
238
+ // [
239
+ // { name: '张三', age: 25 },
240
+ // { name: '李四', age: 30 }
241
+ // ]
242
+ ```
243
+
244
+ **提取数组字段为新数组**:
245
+
246
+ ```typescript
247
+ const schema = {
248
+ type: 'array',
249
+ properties: ['.name', { type: 'string' }]
250
+ };
251
+ const transform = createDataTransformer(schema);
252
+
253
+ transform([
254
+ { name: '张三', age: 25 },
255
+ { name: '李四', age: 30 }
256
+ ]);
257
+ // ['张三', '李四']
107
258
  ```
108
259
 
109
- 当输入为数组时,数组的第一个元素表示字段路径
260
+ #### 字段重命名
261
+
262
+ 使用元组 `[原字段路径, 目标schema]` 实现字段重命名:
263
+
264
+ ```typescript
265
+ const schema = {
266
+ type: 'object',
267
+ properties: {
268
+ fullName: ['name', { type: 'string' }],
269
+ userAge: ['age', { type: 'integer' }]
270
+ }
271
+ };
272
+
273
+ const transform = createDataTransformer(schema);
110
274
 
111
- ```javascript
112
- select(['sub.age', { type: 'integer' }])({
113
- name: 'quan',
114
- sub: { age: 33.3 },
115
- }); // 33
275
+ transform({ name: '王五', age: '28' });
276
+ // { fullName: '王五', userAge: 28 }
116
277
  ```
117
278
 
118
- ### With resolve
279
+ #### resolve 函数
119
280
 
120
- ```javascript
121
- select({ type: 'integer', resolve: (v) => v + 1})(88); // 89
281
+ 使用 `resolve` 自定义转换逻辑:
122
282
 
123
- select({
283
+ ```typescript
284
+ const schema = {
124
285
  type: 'object',
125
286
  properties: {
126
- name: {
287
+ fullName: {
127
288
  type: 'string',
128
- resolve: (a, b) => `${a}_${b.aa}`,
129
- },
130
- age: {
131
- type: 'integer',
132
- resolve: (a) => a + 1,
133
- },
134
- },
135
- })({
136
- name: 'quan',
137
- aa: 'xx',
138
- age: 33,
139
- }); // { "name": "quan_xx", "age": 34 }
140
- ```
141
-
142
- ```javascript
143
- const ret = select(
144
- {
145
- type: 'object',
146
- properties: {
147
- count: {
148
- type: 'integer',
149
- },
150
- list: {
151
- type: 'array',
152
- properties: {
153
- token: ['.', {
154
- type: 'string',
155
- resolve: (d) => `${d.name}_${d.age}`,
156
- }],
157
- name: {
158
- type: 'string',
159
- },
160
- },
161
- },
162
- },
163
- },
164
- )({
165
- count: 20,
166
- list: [
167
- {
168
- name: 'big',
169
- age: 11,
170
- },
171
- {
172
- name: 'bar',
173
- age: 22,
289
+ resolve: (value, root) => `${root.prefix}${value}`
174
290
  },
175
- ],
291
+ total: {
292
+ type: 'number',
293
+ resolve: (value) => (value as number) * 1.1 // 增加 10%
294
+ }
295
+ }
296
+ };
297
+
298
+ const transform = createDataTransformer(schema);
299
+
300
+ transform({
301
+ name: '赵六',
302
+ prefix: '用户-',
303
+ price: '100'
176
304
  });
177
- assert.deepEqual(ret, {
178
- count: 20,
179
- list: [
180
- {
181
- name: 'big',
182
- token: 'big_11',
183
- },
184
- {
185
- name: 'bar',
186
- token: 'bar_22',
305
+ // { fullName: '用户-赵六', total: 110 }
306
+ ```
307
+
308
+ **resolve 参数说明:**
309
+ - `value` - 当前字段的值
310
+ - `root` - 根数据(可用于访问其他字段)
311
+
312
+ #### 空 properties 处理
313
+
314
+ ```typescript
315
+ // 空 properties 对象保留所有字段
316
+ const schema1 = { type: 'object', properties: {} };
317
+ const transform1 = createDataTransformer(schema1);
318
+
319
+ transform1({ a: 1, b: 2 }); // { a: 1, b: 2 }
320
+
321
+ // 空 properties 数组返回空数组
322
+ const schema2 = { type: 'array', properties: {} };
323
+ const transform2 = createDataTransformer(schema2);
324
+
325
+ transform2({ items: [1, 2, 3] }); // []
326
+ ```
327
+
328
+ ### parseValueByType
329
+
330
+ 根据类型定义解析和转换值。
331
+
332
+ ```typescript
333
+ import { parseValueByType } from '@quanxiaoxiao/datav';
334
+ ```
335
+
336
+ | 类型 | 示例输入 | 输出 | 说明 |
337
+ |------|----------|------|------|
338
+ | `string` | `123` | `'123'` | 转换为字符串 |
339
+ | `number` | `'123.45'` | `123.45` | 转换为数字 |
340
+ | `integer` | `'123.45'` | `123` | 转换为整数 |
341
+ | `boolean` | `'true'` | `true` | 转换为布尔值 |
342
+ | `json` | `'{"a":1}'` | `{a:1}` | 解析 JSON |
343
+ | `object` | `'{"a":1}'` | `{a:1}` | 解析 JSON 对象 |
344
+ | `array` | `'[1,2,3]'` | `[1,2,3]` | 解析 JSON 数组 |
345
+
346
+ ```typescript
347
+ parseValueByType('123', 'integer'); // 123
348
+ parseValueByType('123.45', 'number'); // 123.45
349
+ parseValueByType('true', 'boolean'); // true
350
+ parseValueByType('{"name":"test"}', 'json'); // { name: 'test' }
351
+ parseValueByType('[1,2,3]', 'array'); // [1, 2, 3]
352
+ parseValueByType(null, 'string'); // null
353
+ parseValueByType(null, 'array'); // []
354
+ ```
355
+
356
+ ### validateExpressSchema
357
+
358
+ 验证 schema 表达式是否有效,无效时抛出错误:
359
+
360
+ ```typescript
361
+ import { validateExpressSchema } from '@quanxiaoxiao/datav';
362
+
363
+ // 有效 schema
364
+ validateExpressSchema({ type: 'string' });
365
+ validateExpressSchema({ type: 'number' });
366
+ validateExpressSchema({ type: 'object', properties: {} });
367
+ validateExpressSchema({ type: 'array', properties: { name: { type: 'string' } } });
368
+ validateExpressSchema(['path', { type: 'string' }]);
369
+
370
+ // 无效 schema(抛出错误)
371
+ validateExpressSchema({ type: 'object' }); // Error: 缺少 properties
372
+ validateExpressSchema({ type: 'array' }); // Error: 缺少 properties
373
+ validateExpressSchema({ type: 'invalid' }); // Error: 无效类型
374
+ validateExpressSchema(['path']); // Error: 元组需要2个元素
375
+ ```
376
+
377
+ ## 辅助函数
378
+
379
+ ### createDataAccessor
380
+
381
+ 通过路径访问嵌套数据,支持字符串路径和数字索引。
382
+
383
+ ```typescript
384
+ import { createDataAccessor } from '@quanxiaoxiao/datav';
385
+
386
+ // 字符串路径
387
+ const accessor = createDataAccessor('user.profile.name');
388
+ accessor({ user: { profile: { name: '张三' } } }); // '张三'
389
+ accessor({ user: { profile: null } }); // null
390
+ accessor({ user: {} }); // null
391
+
392
+ // 数字索引
393
+ const arrayAccessor = createDataAccessor(0);
394
+ arrayAccessor(['a', 'b', 'c']); // 'a'
395
+ arrayAccessor({ 0: 'first' }); // null(非数组)
396
+
397
+ // 负数索引
398
+ const lastAccessor = createDataAccessor(-1);
399
+ lastAccessor(['a', 'b', 'c']); // 'c'
400
+
401
+ // null/undefined
402
+ const nullAccessor = createDataAccessor(null);
403
+ nullAccessor({}); // null
404
+ ```
405
+
406
+ ### createArrayAccessor
407
+
408
+ 创建数组元素访问器。
409
+
410
+ ```typescript
411
+ import { createArrayAccessor } from '@quanxiaoxiao/datav';
412
+
413
+ // 正数索引
414
+ const accessor = createArrayAccessor(0);
415
+ accessor(['a', 'b', 'c']); // 'a'
416
+
417
+ // 负数索引
418
+ const lastAccessor = createArrayAccessor(-1);
419
+ lastAccessor(['a', 'b', 'c']); // 'c'
420
+
421
+ // 越界索引
422
+ const outOfBounds = createArrayAccessor(10);
423
+ outOfBounds(['a', 'b', 'c']); // null
424
+
425
+ // 数组长度
426
+ const lengthAccessor = createArrayAccessor('length');
427
+ lengthAccessor(['a', 'b', 'c']); // 3
428
+
429
+ // 无效输入
430
+ createArrayAccessor(1.5)(['a', 'b', 'c']); // null(浮点数)
431
+ createArrayAccessor('abc')(['a', 'b']); // null(非数字字符串)
432
+ ```
433
+
434
+ ### createPathAccessor
435
+
436
+ 通过路径段数组访问数据。
437
+
438
+ ```typescript
439
+ import { createPathAccessor } from '@quanxiaoxiao/datav';
440
+
441
+ // 单个键
442
+ const accessor = createPathAccessor(['name']);
443
+ accessor({ name: '张三' }); // '张三'
444
+
445
+ // 嵌套路径
446
+ const nestedAccessor = createPathAccessor(['user', 'profile', 'age']);
447
+ nestedAccessor({ user: { profile: { age: 25 } } }); // 25
448
+
449
+ // 数组访问
450
+ const arrayAccessor = createPathAccessor(['items', '0', 'name']);
451
+ arrayAccessor({ items: [{ name: 'A' }, { name: 'B' }] }); // 'A'
452
+
453
+ // 空路径数组(返回原数据)
454
+ const identityAccessor = createPathAccessor([]);
455
+ identityAccessor({ a: 1 }); // { a: 1 }
456
+ ```
457
+
458
+ ### parseDotPath
459
+
460
+ 解析点分路径字符串。
461
+
462
+ ```typescript
463
+ import { parseDotPath } from '@quanxiaoxiao/datav';
464
+
465
+ parseDotPath('user.profile.name'); // ['user', 'profile', 'name']
466
+ parseDotPath('.user.name'); // ['user', 'name'](前导点被忽略)
467
+ parseDotPath(''); // []
468
+ parseDotPath('name'); // ['name']
469
+
470
+ // 转义点号
471
+ parseDotPath('user\\.name'); // ['user.name']
472
+ parseDotPath('a\\.b.c'); // ['a.b', 'c']
473
+
474
+ // 错误情况
475
+ parseDotPath('user..name'); // Error: 包含空段
476
+ parseDotPath('.user'); // Error: 包含空段
477
+ ```
478
+
479
+ ## 实际应用场景
480
+
481
+ ### API 响应数据转换
482
+
483
+ ```typescript
484
+ const apiResponseSchema = {
485
+ type: 'object',
486
+ properties: {
487
+ code: { type: 'integer' },
488
+ data: {
489
+ type: 'object',
490
+ properties: {
491
+ userId: ['id', { type: 'integer' }],
492
+ userName: ['name', { type: 'string' }],
493
+ email: ['email', { type: 'string' }],
494
+ createdAt: ['created_at', { type: 'string' }]
495
+ }
187
496
  },
188
- ],
189
- });
497
+ message: { type: 'string' }
498
+ }
499
+ };
500
+
501
+ const transform = createDataTransformer(apiResponseSchema);
502
+
503
+ // 原始 API 响应
504
+ const apiResponse = {
505
+ code: 200,
506
+ data: {
507
+ id: '10001',
508
+ name: '张三',
509
+ email: 'zhangsan@example.com',
510
+ created_at: '2024-01-15',
511
+ password: 'secret'
512
+ },
513
+ message: 'success',
514
+ timestamp: 1705315200
515
+ };
516
+
517
+ // 转换后
518
+ const result = transform(apiResponse);
519
+ // {
520
+ // code: 200,
521
+ // data: {
522
+ // userId: 10001,
523
+ // userName: '张三',
524
+ // email: 'zhangsan@example.com',
525
+ // createdAt: '2024-01-15'
526
+ // },
527
+ // message: 'success'
528
+ // }
529
+ ```
530
+
531
+ ### 表单数据处理
532
+
533
+ ```typescript
534
+ const formSchema = {
535
+ type: 'object',
536
+ properties: {
537
+ username: { type: 'string' },
538
+ age: ['age', { type: 'integer' }],
539
+ email: { type: 'string' },
540
+ phone: { type: 'string' }
541
+ }
542
+ };
543
+
544
+ const transform = createDataTransformer(formSchema);
545
+
546
+ const formData = {
547
+ username: ' 张三 ', // 保留空格
548
+ age: '25',
549
+ email: 'zhangsan@example.com',
550
+ phone: '13800138000',
551
+ confirmPassword: '123456' // 忽略
552
+ };
553
+
554
+ transform(formData);
555
+ // {
556
+ // username: ' 张三 ',
557
+ // age: 25,
558
+ // email: 'zhangsan@example.com',
559
+ // phone: '13800138000'
560
+ // }
561
+ ```
562
+
563
+ ### 列表数据提取
564
+
565
+ ```typescript
566
+ const listSchema = {
567
+ type: 'array',
568
+ properties: ['.id', { type: 'integer' }]
569
+ };
570
+
571
+ const transform = createDataTransformer(listSchema);
572
+
573
+ const products = [
574
+ { id: '001', name: '产品A', price: 100 },
575
+ { id: '002', name: '产品B', price: 200 },
576
+ { id: '003', name: '产品C', price: 300 }
577
+ ];
578
+
579
+ transform(products); // [1, 2, 3]
580
+ ```
581
+
582
+ ## API 参考
583
+
584
+ ### createDataTransformer
585
+
586
+ ```typescript
587
+ type SchemaExpress =
588
+ | { type: 'string' | 'number' | 'boolean' | 'integer' }
589
+ | { type: 'object'; properties?: Record<string, SchemaExpress> }
590
+ | { type: 'array'; properties?: Record<string, SchemaExpress> | [string, SchemaExpress> }
591
+ | [string, SchemaExpress];
592
+
593
+ declare const createDataTransformer: (
594
+ schema: SchemaExpress
595
+ ) => (data: unknown, root?: unknown) => unknown;
596
+ ```
597
+
598
+ ### parseValueByType
599
+
600
+ ```typescript
601
+ declare const parseValueByType: (value: unknown, type: DataType) => unknown;
602
+
603
+ type DataType = 'string' | 'number' | 'boolean' | 'integer' | 'json' | 'object' | 'array';
604
+ ```
605
+
606
+ ### createDataAccessor
607
+
608
+ ```typescript
609
+ declare const createDataAccessor: (
610
+ pathname: string | number | null
611
+ ) => (data: unknown) => unknown;
612
+ ```
613
+
614
+ ### createArrayAccessor
615
+
616
+ ```typescript
617
+ declare const createArrayAccessor: (
618
+ index: string | number
619
+ ) => (array: unknown[]) => unknown;
620
+ ```
621
+
622
+ ### createPathAccessor
623
+
624
+ ```typescript
625
+ declare const createPathAccessor: (
626
+ pathSegments: string[]
627
+ ) => (data: unknown) => unknown;
628
+ ```
629
+
630
+ ### parseDotPath
631
+
632
+ ```typescript
633
+ declare const parseDotPath: (path: string) => string[];
634
+ ```
635
+
636
+ ### validateExpressSchema
637
+
638
+ ```typescript
639
+ declare const validateExpressSchema: (schema: ExpressSchema) => void;
640
+
641
+ interface ExpressSchema {
642
+ type: 'string' | 'number' | 'boolean' | 'integer' | 'object' | 'array';
643
+ properties?: Record<string, unknown> | [string, object];
644
+ resolve?: (value: unknown, root: unknown) => unknown;
645
+ }
646
+ ```
647
+
648
+ ## 常见问题
649
+
650
+ ### Q: 如何忽略某个字段?
651
+
652
+ A: 不在 schema 的 `properties` 中定义该字段即可。
653
+
654
+ ### Q: 如何处理嵌套数组?
655
+
656
+ A: 组合使用对象类型和数组类型:
657
+
658
+ ```typescript
659
+ const schema = {
660
+ type: 'object',
661
+ properties: {
662
+ users: {
663
+ type: 'array',
664
+ properties: {
665
+ name: { type: 'string' },
666
+ tags: {
667
+ type: 'array',
668
+ properties: ['.', { type: 'string' }]
669
+ }
670
+ }
671
+ }
672
+ }
673
+ };
190
674
  ```
675
+
676
+ ### Q: 如何访问数组的特定元素?
677
+
678
+ A: 使用路径访问语法:
679
+
680
+ ```typescript
681
+ const schema = {
682
+ type: 'object',
683
+ properties: {
684
+ firstItem: ['items.0', { type: 'string' }],
685
+ secondItem: ['items.1', { type: 'string' }]
686
+ }
687
+ };
688
+ ```
689
+
690
+ ### Q: `resolve` 和普通转换可以同时使用吗?
691
+
692
+ A: 可以。`resolve` 在类型转换之后执行:
693
+
694
+ ```typescript
695
+ const schema = {
696
+ type: 'string',
697
+ resolve: (value) => value.toUpperCase()
698
+ };
699
+
700
+ createDataTransformer(schema)('hello'); // 'HELLO'
701
+ ```
702
+
703
+ ## 许可证
704
+
705
+ MIT