@quanxiaoxiao/datav 0.5.0 → 0.6.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 (94) hide show
  1. package/README.md +215 -555
  2. package/dist/create-data-accessor.d.ts +7 -0
  3. package/dist/create-data-accessor.d.ts.map +1 -0
  4. package/dist/create-data-accessor.js +74 -0
  5. package/dist/create-data-accessor.js.map +1 -0
  6. package/dist/createDataAccessor.d.ts +5 -1
  7. package/dist/createDataAccessor.d.ts.map +1 -1
  8. package/dist/createDataAccessor.js +52 -13
  9. package/dist/createDataAccessor.js.map +1 -1
  10. package/dist/createDataTransformer.d.ts +1 -10
  11. package/dist/createDataTransformer.d.ts.map +1 -1
  12. package/dist/createDataTransformer.js +56 -33
  13. package/dist/createDataTransformer.js.map +1 -1
  14. package/dist/data-accessor.d.ts +7 -0
  15. package/dist/data-accessor.d.ts.map +1 -0
  16. package/dist/data-accessor.js +74 -0
  17. package/dist/data-accessor.js.map +1 -0
  18. package/dist/dot-path.d.ts +2 -0
  19. package/dist/dot-path.d.ts.map +1 -0
  20. package/dist/dot-path.js +65 -0
  21. package/dist/dot-path.js.map +1 -0
  22. package/dist/errors.d.ts +42 -0
  23. package/dist/errors.d.ts.map +1 -0
  24. package/dist/errors.js +121 -0
  25. package/dist/errors.js.map +1 -0
  26. package/dist/field-dsl.d.ts +36 -0
  27. package/dist/field-dsl.d.ts.map +1 -0
  28. package/dist/field-dsl.js +91 -0
  29. package/dist/field-dsl.js.map +1 -0
  30. package/dist/index.d.ts +4 -4
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +4 -4
  33. package/dist/index.js.map +1 -1
  34. package/dist/parse-dot-path.d.ts +2 -0
  35. package/dist/parse-dot-path.d.ts.map +1 -0
  36. package/dist/parse-dot-path.js +65 -0
  37. package/dist/parse-dot-path.js.map +1 -0
  38. package/dist/parse-value-by-type.d.ts +18 -0
  39. package/dist/parse-value-by-type.d.ts.map +1 -0
  40. package/dist/parse-value-by-type.js +140 -0
  41. package/dist/parse-value-by-type.js.map +1 -0
  42. package/dist/parseDotPath.d.ts +1 -1
  43. package/dist/parseDotPath.d.ts.map +1 -1
  44. package/dist/parseDotPath.js +59 -8
  45. package/dist/parseDotPath.js.map +1 -1
  46. package/dist/parseValueByType.d.ts +9 -2
  47. package/dist/parseValueByType.d.ts.map +1 -1
  48. package/dist/parseValueByType.js +85 -67
  49. package/dist/parseValueByType.js.map +1 -1
  50. package/dist/schema.d.ts +71 -0
  51. package/dist/schema.d.ts.map +1 -0
  52. package/dist/schema.js +146 -0
  53. package/dist/schema.js.map +1 -0
  54. package/dist/transformer.d.ts +28 -0
  55. package/dist/transformer.d.ts.map +1 -0
  56. package/dist/transformer.js +148 -0
  57. package/dist/transformer.js.map +1 -0
  58. package/dist/validateExpressSchema.d.ts +11 -2
  59. package/dist/validateExpressSchema.d.ts.map +1 -1
  60. package/dist/validateExpressSchema.js +114 -49
  61. package/dist/validateExpressSchema.js.map +1 -1
  62. package/dist/value-type.d.ts +18 -0
  63. package/dist/value-type.d.ts.map +1 -0
  64. package/dist/value-type.js +140 -0
  65. package/dist/value-type.js.map +1 -0
  66. package/package.json +4 -6
  67. package/src/data-accessor.test.ts +500 -0
  68. package/src/data-accessor.ts +96 -0
  69. package/src/dot-path.test.ts +239 -0
  70. package/src/dot-path.ts +78 -0
  71. package/src/errors.test.ts +199 -0
  72. package/src/errors.ts +159 -0
  73. package/src/field-dsl.test.ts +1754 -0
  74. package/src/field-dsl.ts +179 -0
  75. package/src/index.ts +40 -8
  76. package/src/transformer.test.ts +1521 -0
  77. package/src/transformer.ts +298 -0
  78. package/src/utils.test.ts +1 -1
  79. package/src/value-type.test.ts +553 -0
  80. package/src/value-type.ts +198 -0
  81. package/src/createArrayAccessor.test.ts +0 -181
  82. package/src/createArrayAccessor.ts +0 -48
  83. package/src/createDataAccessor.test.ts +0 -220
  84. package/src/createDataAccessor.ts +0 -26
  85. package/src/createDataTransformer.test.ts +0 -847
  86. package/src/createDataTransformer.ts +0 -173
  87. package/src/createPathAccessor.test.ts +0 -217
  88. package/src/createPathAccessor.ts +0 -45
  89. package/src/parseDotPath.test.ts +0 -132
  90. package/src/parseDotPath.ts +0 -13
  91. package/src/parseValueByType.test.ts +0 -342
  92. package/src/parseValueByType.ts +0 -165
  93. package/src/validateExpressSchema.test.ts +0 -295
  94. package/src/validateExpressSchema.ts +0 -62
package/README.md CHANGED
@@ -4,15 +4,16 @@
4
4
  [![npm license](https://img.shields.io/npm/l/@quanxiaoxiao/datav.svg)](https://opensource.org/licenses/MIT)
5
5
  [![node version](https://img.shields.io/node/v/@quanxiaoxiao/datav.svg)](https://nodejs.org)
6
6
 
7
- 数据转换工具库,支持类型转换、数据路径访问和 schema 驱动的数据转换。
7
+ 轻量级数据转换工具库,支持 **Schema 驱动** 和 **Field DSL** 两种转换模式,提供类型安全的数据提取、转换和组合能力。
8
8
 
9
- ## 特性
9
+ ## 核心特性
10
10
 
11
- - **类型安全** - 完整的 TypeScript 类型支持
12
- - **Schema 驱动** - 通过声明式 schema 定义转换规则
13
- - **路径访问** - 支持点分路径访问嵌套数据
14
- - **根路径引用** - 使用 `$` 从根数据访问
15
- - **轻量级** - 零额外依赖
11
+ - **双模式转换**: 支持声明式 Schema 和链式 Field DSL 两种方式
12
+ - **类型安全**: 完整的 TypeScript 类型支持,编译时检查
13
+ - **路径访问**: 强大的点分隔符路径访问,支持嵌套和数组索引
14
+ - **类型转换**: 自动类型转换,处理 string/number/boolean/integer
15
+ - **默认值支持**: 支持 `defaultValue`,在数据缺失或为空时提供默认值
16
+ - **错误处理**: 完善的错误机制,提供详细的错误信息
16
17
 
17
18
  ## 安装
18
19
 
@@ -22,682 +23,341 @@ npm install @quanxiaoxiao/datav
22
23
 
23
24
  ## 快速开始
24
25
 
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
- ## 核心功能
56
-
57
- ### createDataTransformer
26
+ ### Schema 驱动模式
58
27
 
59
- 根据 schema 定义将数据从一种格式转换为另一种格式。
28
+ 通过定义 Schema 来描述数据结构:
60
29
 
61
30
  ```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
- #### 对象转换
31
+ import { transform } from '@quanxiaoxiao/datav';
82
32
 
83
- 提取并转换对象的指定字段:
84
-
85
- ```typescript
86
33
  const schema = {
34
+ path: '.',
87
35
  type: 'object',
88
36
  properties: {
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
- }
123
- }
124
- }
125
- }
126
- }
37
+ name: { path: '.name', type: 'string' },
38
+ age: { path: '.age', type: 'integer' },
39
+ active: { path: '.active', type: 'boolean' },
40
+ },
127
41
  };
128
42
 
129
- const transform = createDataTransformer(schema);
130
-
131
- transform({
132
- user: {
133
- name: '王五',
134
- profile: {
135
- age: '30',
136
- city: '上海市',
137
- phone: '13700137000'
138
- }
139
- }
43
+ const result = transform(schema, {
44
+ name: 123,
45
+ age: '30',
46
+ active: 'true',
140
47
  });
141
- // { user: { name: '王五', profile: { age: 30, city: '上海市' } } }
48
+ // { name: '123', age: 30, active: true }
142
49
  ```
143
50
 
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);
51
+ #### 默认值 (defaultValue)
169
52
 
170
- transform({ items: ['a', 'b', 'c'], other: 'data' });
171
- // ['a', 'b', 'c']
172
- ```
53
+ 当数据缺失或为空时,可以提供默认值:
173
54
 
174
55
  ```typescript
175
- // 组合使用路径和根路径
176
56
  const schema = {
57
+ path: '.',
177
58
  type: 'object',
178
59
  properties: {
179
- userId: ['.$id', { type: 'integer' }],
60
+ name: { path: '.name', type: 'string', defaultValue: 'Anonymous' },
61
+ count: { path: '.count', type: 'number', defaultValue: 0 },
180
62
  items: {
63
+ path: '.items',
181
64
  type: 'array',
182
- properties: {
183
- name: { type: 'string' },
184
- userName: ['$userName', { type: 'string' }]
185
- }
186
- }
187
- }
65
+ items: { path: '.', type: 'string' },
66
+ defaultValue: ['default item'],
67
+ },
68
+ },
188
69
  };
189
70
 
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
- // }
71
+ transform(schema, { name: null, count: null, items: [] });
72
+ // { name: 'Anonymous', count: 0, items: ['default item'] }
207
73
  ```
208
74
 
209
- #### 数组转换
75
+ ### Field DSL 模式
210
76
 
211
- **元组形式** - 转换每个数组元素:
77
+ 通过链式 API 构建转换规则:
212
78
 
213
79
  ```typescript
214
- // 转换字符串数组为整数数组
215
- const schema = {
216
- type: 'array',
217
- properties: ['.', { type: 'integer' }]
218
- };
219
- const transform = createDataTransformer(schema);
80
+ import { compile, toObject, toString, toInteger, toArray } from '@quanxiaoxiao/datav';
220
81
 
221
- transform(['1.1', '2.9', '3']); // [1, 2, 3]
222
- transform(['10', '20', '30']); // [10, 20, 30]
223
- ```
224
-
225
- **对象形式** - 提取数组中对象的指定字段:
82
+ const field = toObject({
83
+ name: toString('user.name'),
84
+ age: toInteger('user.age'),
85
+ tags: toArray('user.tags'),
86
+ });
226
87
 
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
- // ]
88
+ const result = compile(field)({
89
+ user: { name: 'Alice', age: '25', tags: [1, 2, 3] },
90
+ });
91
+ // { name: 'Alice', age: 25, tags: ['1', '2', '3'] }
242
92
  ```
243
93
 
244
- **提取数组字段为新数组**:
245
-
246
- ```typescript
247
- const schema = {
248
- type: 'array',
249
- properties: ['.name', { type: 'string' }]
250
- };
251
- const transform = createDataTransformer(schema);
94
+ ## API 参考
252
95
 
253
- transform([
254
- { name: '张三', age: 25 },
255
- { name: '李四', age: 30 }
256
- ]);
257
- // ['张三', '李四']
258
- ```
96
+ ### Schema 驱动模式
259
97
 
260
- #### 字段重命名
98
+ #### transform(schema, data)
261
99
 
262
- 使用元组 `[原字段路径, 目标schema]` 实现字段重命名:
100
+ 一次性转换数据:
263
101
 
264
102
  ```typescript
265
103
  const schema = {
104
+ path: '.',
266
105
  type: 'object',
267
106
  properties: {
268
- fullName: ['name', { type: 'string' }],
269
- userAge: ['age', { type: 'integer' }]
270
- }
107
+ id: { path: '.id', type: 'string' },
108
+ items: {
109
+ path: '.items',
110
+ type: 'array',
111
+ items: { path: '.', type: 'integer' },
112
+ },
113
+ },
271
114
  };
272
115
 
273
- const transform = createDataTransformer(schema);
274
-
275
- transform({ name: '王五', age: '28' });
276
- // { fullName: '王五', userAge: 28 }
116
+ transform(schema, { id: 123, items: ['1', '2', '3'] });
117
+ // { id: '123', items: [1, 2, 3] }
277
118
  ```
278
119
 
279
- #### resolve 函数
120
+ #### createTransform(schema)
280
121
 
281
- 使用 `resolve` 自定义转换逻辑:
122
+ 创建可复用的转换函数:
282
123
 
283
124
  ```typescript
284
- const schema = {
125
+ const userTransformer = createTransform({
126
+ path: '.',
285
127
  type: 'object',
286
128
  properties: {
287
- fullName: {
288
- type: 'string',
289
- resolve: (value, root) => `${root.prefix}${value}`
290
- },
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'
129
+ name: { path: '.name', type: 'string' },
130
+ age: { path: '.age', type: 'integer' },
131
+ },
304
132
  });
305
- // { fullName: '用户-赵六', total: 110 }
306
- ```
307
133
 
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] }); // []
134
+ userTransformer({ name: 'Bob', age: '30' }); // { name: 'Bob', age: 30 }
135
+ userTransformer({ name: 'Carol', age: '25' }); // { name: 'Carol', age: 25 }
326
136
  ```
327
137
 
328
- ### parseValueByType
329
-
330
- 根据类型定义解析和转换值。
331
-
332
- ```typescript
333
- import { parseValueByType } from '@quanxiaoxiao/datav';
334
- ```
138
+ #### validateExpressSchema(schema)
335
139
 
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 数组 |
140
+ 验证 Schema 格式:
345
141
 
346
142
  ```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'); // []
143
+ const result = validateExpressSchema({
144
+ path: '.',
145
+ type: 'object',
146
+ properties: {
147
+ name: { path: '.name', type: 'string' },
148
+ },
149
+ });
150
+ // { valid: true, errors: [] }
354
151
  ```
355
152
 
356
- ### validateExpressSchema
153
+ ### Field DSL 模式
357
154
 
358
- 验证 schema 表达式是否有效,无效时抛出错误:
155
+ #### 基本类型转换
359
156
 
360
157
  ```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个元素
158
+ toString(path?) // 转换为字符串
159
+ toNumber(path?) // 转换为数字
160
+ toInteger(path?) // 转换为整数
161
+ toBoolean(path?) // 转换为布尔值
375
162
  ```
376
163
 
377
- ## 辅助函数
378
-
379
- ### createDataAccessor
380
-
381
- 通过路径访问嵌套数据,支持字符串路径和数字索引。
164
+ #### 复合类型
382
165
 
383
166
  ```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
167
+ toObject(pathOrFields, fields?) // 对象类型
168
+ toArray(pathOrField, field?) // 数组类型
404
169
  ```
405
170
 
406
- ### createArrayAccessor
171
+ #### compile(field)
407
172
 
408
- 创建数组元素访问器。
173
+ 将 Field 编译为可执行函数:
409
174
 
410
175
  ```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
176
+ const field = toObject({
177
+ name: toString('user.name'),
178
+ age: toInteger('user.age'),
179
+ });
428
180
 
429
- // 无效输入
430
- createArrayAccessor(1.5)(['a', 'b', 'c']); // null(浮点数)
431
- createArrayAccessor('abc')(['a', 'b']); // null(非数字字符串)
181
+ const executor = compile(field);
182
+ executor({ user: { name: 'Alice', age: '25' } });
183
+ // { name: 'Alice', age: 25 }
432
184
  ```
433
185
 
434
- ### createPathAccessor
186
+ ### 路径访问
435
187
 
436
- 通过路径段数组访问数据。
188
+ 支持点分隔符路径和数组索引:
437
189
 
438
190
  ```typescript
439
- import { createPathAccessor } from '@quanxiaoxiao/datav';
440
-
441
- // 单个键
442
- const accessor = createPathAccessor(['name']);
443
- accessor({ name: '张三' }); // '张三'
444
-
445
191
  // 嵌套路径
446
- const nestedAccessor = createPathAccessor(['user', 'profile', 'age']);
447
- nestedAccessor({ user: { profile: { age: 25 } } }); // 25
192
+ toString('user.profile.name')
448
193
 
449
- // 数组访问
450
- const arrayAccessor = createPathAccessor(['items', '0', 'name']);
451
- arrayAccessor({ items: [{ name: 'A' }, { name: 'B' }] }); // 'A'
194
+ // 数组索引
195
+ toString('items.0.name')
196
+ toString('matrix.0.1.value')
452
197
 
453
- // 空路径数组(返回原数据)
454
- const identityAccessor = createPathAccessor([]);
455
- identityAccessor({ a: 1 }); // { a: 1 }
198
+ // 根路径
199
+ toString('$')
456
200
  ```
457
201
 
458
- ### parseDotPath
459
-
460
- 解析点分路径字符串。
202
+ ### 错误处理
461
203
 
462
204
  ```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']
205
+ import { DataVError, isDataVError, ERROR_CODES } from '@quanxiaoxiao/datav';
469
206
 
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: 包含空段
207
+ try {
208
+ transform(schema, data);
209
+ } catch (error) {
210
+ if (isDataVError(error)) {
211
+ console.log(error.code); // 错误码
212
+ console.log(error.details); // 详细错误信息
213
+ }
214
+ }
477
215
  ```
478
216
 
479
- ## 实际应用场景
217
+ ## 高级用法
480
218
 
481
- ### API 响应数据转换
219
+ ### 嵌套对象转换
482
220
 
483
221
  ```typescript
484
- const apiResponseSchema = {
222
+ const schema = {
223
+ path: '.',
485
224
  type: 'object',
486
225
  properties: {
487
- code: { type: 'integer' },
488
- data: {
226
+ user: {
227
+ path: '.user',
489
228
  type: 'object',
490
229
  properties: {
491
- userId: ['id', { type: 'integer' }],
492
- userName: ['name', { type: 'string' }],
493
- email: ['email', { type: 'string' }],
494
- createdAt: ['created_at', { type: 'string' }]
495
- }
230
+ id: { path: '.id', type: 'integer' },
231
+ name: { path: '.name', type: 'string' },
232
+ address: {
233
+ path: '.address',
234
+ type: 'object',
235
+ properties: {
236
+ city: { path: '.city', type: 'string' },
237
+ zipCode: { path: '.zip', type: 'integer' },
238
+ },
239
+ },
240
+ },
496
241
  },
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
242
  },
513
- message: 'success',
514
- timestamp: 1705315200
515
243
  };
516
244
 
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
- // }
245
+ transform(schema, {
246
+ user: {
247
+ id: '42',
248
+ name: 'Alice',
249
+ address: { city: 'New York', zip: '10001' },
250
+ },
251
+ });
252
+ // { user: { id: 42, name: 'Alice', address: { city: 'New York', zip: 10001 } } }
561
253
  ```
562
254
 
563
- ### 列表数据提取
255
+ ### 数组转换
564
256
 
565
257
  ```typescript
566
- const listSchema = {
258
+ const schema = {
259
+ path: '.users',
567
260
  type: 'array',
568
- properties: ['.id', { type: 'integer' }]
261
+ items: {
262
+ path: '.',
263
+ type: 'object',
264
+ properties: {
265
+ id: { path: '.id', type: 'integer' },
266
+ name: { path: '.name', type: 'string' },
267
+ },
268
+ },
569
269
  };
570
270
 
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;
271
+ transform(schema, {
272
+ users: [
273
+ { id: '1', name: 'A' },
274
+ { id: '2', name: 'B' },
275
+ ],
276
+ });
277
+ // [{ id: 1, name: 'A' }, { id: 2, name: 'B' }]
612
278
  ```
613
279
 
614
- ### createArrayAccessor
280
+ ### 自定义 Resolver
615
281
 
616
- ```typescript
617
- declare const createArrayAccessor: (
618
- index: string | number
619
- ) => (array: unknown[]) => unknown;
620
- ```
621
-
622
- ### createPathAccessor
282
+ 通过 `resolve` 字段添加自定义转换逻辑:
623
283
 
624
284
  ```typescript
625
- declare const createPathAccessor: (
626
- pathSegments: string[]
627
- ) => (data: unknown) => unknown;
628
- ```
629
-
630
- ### parseDotPath
285
+ const schema = {
286
+ path: '.',
287
+ type: 'object',
288
+ properties: {
289
+ amount: {
290
+ path: '.amount',
291
+ type: 'number',
292
+ resolve: (value) => (value as number) * 100,
293
+ },
294
+ },
295
+ };
631
296
 
632
- ```typescript
633
- declare const parseDotPath: (path: string) => string[];
297
+ transform(schema, { amount: 10 });
298
+ // { amount: 1000 }
634
299
  ```
635
300
 
636
- ### validateExpressSchema
301
+ ## 类型定义
637
302
 
638
303
  ```typescript
639
- declare const validateExpressSchema: (schema: ExpressSchema) => void;
304
+ type SchemaType = 'string' | 'number' | 'boolean' | 'integer' | 'object' | 'array';
640
305
 
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;
306
+ interface SchemaExpress {
307
+ path: string;
308
+ type: SchemaType;
309
+ resolve?: (value: unknown, context: { data: unknown; rootData: unknown; path: string }) => unknown;
310
+ defaultValue?: string | number | boolean | Record<string, unknown> | unknown[];
311
+ properties?: Record<string, SchemaExpress>;
312
+ items?: SchemaExpress;
645
313
  }
646
314
  ```
647
315
 
648
- ## 常见问题
649
-
650
- ### Q: 如何忽略某个字段?
651
-
652
- A: 不在 schema 的 `properties` 中定义该字段即可。
316
+ ### 默认值 (defaultValue)
653
317
 
654
- ### Q: 如何处理嵌套数组?
655
-
656
- A: 组合使用对象类型和数组类型:
318
+ Schema 支持 `defaultValue` 字段,当原始数据缺失、为空或为 `null` 时,将使用默认值:
657
319
 
658
320
  ```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
- }
321
+ // primitive 类型
322
+ const schema1 = {
323
+ path: '.name',
324
+ type: 'string',
325
+ defaultValue: 'Unknown',
673
326
  };
674
- ```
675
-
676
- ### Q: 如何访问数组的特定元素?
677
327
 
678
- A: 使用路径访问语法:
328
+ // array 类型 - 空数组时使用默认值
329
+ const schema2 = {
330
+ path: '.tags',
331
+ type: 'array',
332
+ items: { path: '.', type: 'string' },
333
+ defaultValue: ['untagged'],
334
+ };
679
335
 
680
- ```typescript
681
- const schema = {
336
+ // object 类型 - null 时使用默认值
337
+ const schema3 = {
338
+ path: '.config',
682
339
  type: 'object',
683
- properties: {
684
- firstItem: ['items.0', { type: 'string' }],
685
- secondItem: ['items.1', { type: 'string' }]
686
- }
340
+ properties: { theme: { path: 'theme', type: 'string' } },
341
+ defaultValue: { theme: 'light' },
687
342
  };
688
- ```
689
-
690
- ### Q: `resolve` 和普通转换可以同时使用吗?
691
343
 
692
- A: 可以。`resolve` 在类型转换之后执行:
693
-
694
- ```typescript
695
- const schema = {
696
- type: 'string',
697
- resolve: (value) => value.toUpperCase()
344
+ // 嵌套结构中的 defaultValue
345
+ const schema4 = {
346
+ path: '.',
347
+ type: 'object',
348
+ properties: {
349
+ user: {
350
+ path: '.user',
351
+ type: 'object',
352
+ properties: {
353
+ name: { path: '.name', type: 'string', defaultValue: 'Guest' },
354
+ },
355
+ },
356
+ },
698
357
  };
699
358
 
700
- createDataTransformer(schema)('hello'); // 'HELLO'
359
+ transform(schema4, { user: { name: null } });
360
+ // { user: { name: 'Guest' } }
701
361
  ```
702
362
 
703
363
  ## 许可证