@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.
- package/README.md +215 -555
- package/dist/create-data-accessor.d.ts +7 -0
- package/dist/create-data-accessor.d.ts.map +1 -0
- package/dist/create-data-accessor.js +74 -0
- package/dist/create-data-accessor.js.map +1 -0
- package/dist/createDataAccessor.d.ts +5 -1
- package/dist/createDataAccessor.d.ts.map +1 -1
- package/dist/createDataAccessor.js +52 -13
- package/dist/createDataAccessor.js.map +1 -1
- package/dist/createDataTransformer.d.ts +1 -10
- package/dist/createDataTransformer.d.ts.map +1 -1
- package/dist/createDataTransformer.js +56 -33
- package/dist/createDataTransformer.js.map +1 -1
- package/dist/data-accessor.d.ts +7 -0
- package/dist/data-accessor.d.ts.map +1 -0
- package/dist/data-accessor.js +74 -0
- package/dist/data-accessor.js.map +1 -0
- package/dist/dot-path.d.ts +2 -0
- package/dist/dot-path.d.ts.map +1 -0
- package/dist/dot-path.js +65 -0
- package/dist/dot-path.js.map +1 -0
- package/dist/errors.d.ts +42 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +121 -0
- package/dist/errors.js.map +1 -0
- package/dist/field-dsl.d.ts +36 -0
- package/dist/field-dsl.d.ts.map +1 -0
- package/dist/field-dsl.js +91 -0
- package/dist/field-dsl.js.map +1 -0
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/parse-dot-path.d.ts +2 -0
- package/dist/parse-dot-path.d.ts.map +1 -0
- package/dist/parse-dot-path.js +65 -0
- package/dist/parse-dot-path.js.map +1 -0
- package/dist/parse-value-by-type.d.ts +18 -0
- package/dist/parse-value-by-type.d.ts.map +1 -0
- package/dist/parse-value-by-type.js +140 -0
- package/dist/parse-value-by-type.js.map +1 -0
- package/dist/parseDotPath.d.ts +1 -1
- package/dist/parseDotPath.d.ts.map +1 -1
- package/dist/parseDotPath.js +59 -8
- package/dist/parseDotPath.js.map +1 -1
- package/dist/parseValueByType.d.ts +9 -2
- package/dist/parseValueByType.d.ts.map +1 -1
- package/dist/parseValueByType.js +85 -67
- package/dist/parseValueByType.js.map +1 -1
- package/dist/schema.d.ts +71 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +146 -0
- package/dist/schema.js.map +1 -0
- package/dist/transformer.d.ts +28 -0
- package/dist/transformer.d.ts.map +1 -0
- package/dist/transformer.js +148 -0
- package/dist/transformer.js.map +1 -0
- package/dist/validateExpressSchema.d.ts +11 -2
- package/dist/validateExpressSchema.d.ts.map +1 -1
- package/dist/validateExpressSchema.js +114 -49
- package/dist/validateExpressSchema.js.map +1 -1
- package/dist/value-type.d.ts +18 -0
- package/dist/value-type.d.ts.map +1 -0
- package/dist/value-type.js +140 -0
- package/dist/value-type.js.map +1 -0
- package/package.json +4 -6
- package/src/data-accessor.test.ts +500 -0
- package/src/data-accessor.ts +96 -0
- package/src/dot-path.test.ts +239 -0
- package/src/dot-path.ts +78 -0
- package/src/errors.test.ts +199 -0
- package/src/errors.ts +159 -0
- package/src/field-dsl.test.ts +1754 -0
- package/src/field-dsl.ts +179 -0
- package/src/index.ts +40 -8
- package/src/transformer.test.ts +1521 -0
- package/src/transformer.ts +298 -0
- package/src/utils.test.ts +1 -1
- package/src/value-type.test.ts +553 -0
- package/src/value-type.ts +198 -0
- package/src/createArrayAccessor.test.ts +0 -181
- package/src/createArrayAccessor.ts +0 -48
- package/src/createDataAccessor.test.ts +0 -220
- package/src/createDataAccessor.ts +0 -26
- package/src/createDataTransformer.test.ts +0 -847
- package/src/createDataTransformer.ts +0 -173
- package/src/createPathAccessor.test.ts +0 -217
- package/src/createPathAccessor.ts +0 -45
- package/src/parseDotPath.test.ts +0 -132
- package/src/parseDotPath.ts +0 -13
- package/src/parseValueByType.test.ts +0 -342
- package/src/parseValueByType.ts +0 -165
- package/src/validateExpressSchema.test.ts +0 -295
- package/src/validateExpressSchema.ts +0 -62
package/README.md
CHANGED
|
@@ -4,15 +4,16 @@
|
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
[](https://nodejs.org)
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
轻量级数据转换工具库,支持 **Schema 驱动** 和 **Field DSL** 两种转换模式,提供类型安全的数据提取、转换和组合能力。
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## 核心特性
|
|
10
10
|
|
|
11
|
-
-
|
|
12
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
28
|
+
通过定义 Schema 来描述数据结构:
|
|
60
29
|
|
|
61
30
|
```typescript
|
|
62
|
-
import {
|
|
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
|
-
|
|
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
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
// {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
}
|
|
187
|
-
}
|
|
65
|
+
items: { path: '.', type: 'string' },
|
|
66
|
+
defaultValue: ['default item'],
|
|
67
|
+
},
|
|
68
|
+
},
|
|
188
69
|
};
|
|
189
70
|
|
|
190
|
-
|
|
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
|
-
|
|
222
|
-
|
|
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
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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
|
-
|
|
254
|
-
{ name: '张三', age: 25 },
|
|
255
|
-
{ name: '李四', age: 30 }
|
|
256
|
-
]);
|
|
257
|
-
// ['张三', '李四']
|
|
258
|
-
```
|
|
96
|
+
### Schema 驱动模式
|
|
259
97
|
|
|
260
|
-
####
|
|
98
|
+
#### transform(schema, data)
|
|
261
99
|
|
|
262
|
-
|
|
100
|
+
一次性转换数据:
|
|
263
101
|
|
|
264
102
|
```typescript
|
|
265
103
|
const schema = {
|
|
104
|
+
path: '.',
|
|
266
105
|
type: 'object',
|
|
267
106
|
properties: {
|
|
268
|
-
|
|
269
|
-
|
|
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
|
-
|
|
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
|
-
####
|
|
120
|
+
#### createTransform(schema)
|
|
280
121
|
|
|
281
|
-
|
|
122
|
+
创建可复用的转换函数:
|
|
282
123
|
|
|
283
124
|
```typescript
|
|
284
|
-
const
|
|
125
|
+
const userTransformer = createTransform({
|
|
126
|
+
path: '.',
|
|
285
127
|
type: 'object',
|
|
286
128
|
properties: {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
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
|
-
|
|
309
|
-
|
|
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
|
-
|
|
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
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
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
|
-
###
|
|
153
|
+
### Field DSL 模式
|
|
357
154
|
|
|
358
|
-
|
|
155
|
+
#### 基本类型转换
|
|
359
156
|
|
|
360
157
|
```typescript
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
//
|
|
364
|
-
|
|
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
|
-
|
|
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
|
-
|
|
171
|
+
#### compile(field)
|
|
407
172
|
|
|
408
|
-
|
|
173
|
+
将 Field 编译为可执行函数:
|
|
409
174
|
|
|
410
175
|
```typescript
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
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
|
-
|
|
431
|
-
|
|
181
|
+
const executor = compile(field);
|
|
182
|
+
executor({ user: { name: 'Alice', age: '25' } });
|
|
183
|
+
// { name: 'Alice', age: 25 }
|
|
432
184
|
```
|
|
433
185
|
|
|
434
|
-
###
|
|
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
|
-
|
|
447
|
-
nestedAccessor({ user: { profile: { age: 25 } } }); // 25
|
|
192
|
+
toString('user.profile.name')
|
|
448
193
|
|
|
449
|
-
//
|
|
450
|
-
|
|
451
|
-
|
|
194
|
+
// 数组索引
|
|
195
|
+
toString('items.0.name')
|
|
196
|
+
toString('matrix.0.1.value')
|
|
452
197
|
|
|
453
|
-
//
|
|
454
|
-
|
|
455
|
-
identityAccessor({ a: 1 }); // { a: 1 }
|
|
198
|
+
// 根路径
|
|
199
|
+
toString('$')
|
|
456
200
|
```
|
|
457
201
|
|
|
458
|
-
###
|
|
459
|
-
|
|
460
|
-
解析点分路径字符串。
|
|
202
|
+
### 错误处理
|
|
461
203
|
|
|
462
204
|
```typescript
|
|
463
|
-
import {
|
|
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
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
//
|
|
475
|
-
|
|
476
|
-
|
|
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
|
-
###
|
|
219
|
+
### 嵌套对象转换
|
|
482
220
|
|
|
483
221
|
```typescript
|
|
484
|
-
const
|
|
222
|
+
const schema = {
|
|
223
|
+
path: '.',
|
|
485
224
|
type: 'object',
|
|
486
225
|
properties: {
|
|
487
|
-
|
|
488
|
-
|
|
226
|
+
user: {
|
|
227
|
+
path: '.user',
|
|
489
228
|
type: 'object',
|
|
490
229
|
properties: {
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
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
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
//
|
|
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
|
|
258
|
+
const schema = {
|
|
259
|
+
path: '.users',
|
|
567
260
|
type: 'array',
|
|
568
|
-
|
|
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
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
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
|
-
###
|
|
280
|
+
### 自定义 Resolver
|
|
615
281
|
|
|
616
|
-
|
|
617
|
-
declare const createArrayAccessor: (
|
|
618
|
-
index: string | number
|
|
619
|
-
) => (array: unknown[]) => unknown;
|
|
620
|
-
```
|
|
621
|
-
|
|
622
|
-
### createPathAccessor
|
|
282
|
+
通过 `resolve` 字段添加自定义转换逻辑:
|
|
623
283
|
|
|
624
284
|
```typescript
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
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
|
-
|
|
633
|
-
|
|
297
|
+
transform(schema, { amount: 10 });
|
|
298
|
+
// { amount: 1000 }
|
|
634
299
|
```
|
|
635
300
|
|
|
636
|
-
|
|
301
|
+
## 类型定义
|
|
637
302
|
|
|
638
303
|
```typescript
|
|
639
|
-
|
|
304
|
+
type SchemaType = 'string' | 'number' | 'boolean' | 'integer' | 'object' | 'array';
|
|
640
305
|
|
|
641
|
-
interface
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
resolve?: (value: 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
|
-
|
|
655
|
-
|
|
656
|
-
A: 组合使用对象类型和数组类型:
|
|
318
|
+
Schema 支持 `defaultValue` 字段,当原始数据缺失、为空或为 `null` 时,将使用默认值:
|
|
657
319
|
|
|
658
320
|
```typescript
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
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
|
-
|
|
328
|
+
// array 类型 - 空数组时使用默认值
|
|
329
|
+
const schema2 = {
|
|
330
|
+
path: '.tags',
|
|
331
|
+
type: 'array',
|
|
332
|
+
items: { path: '.', type: 'string' },
|
|
333
|
+
defaultValue: ['untagged'],
|
|
334
|
+
};
|
|
679
335
|
|
|
680
|
-
|
|
681
|
-
const
|
|
336
|
+
// object 类型 - null 时使用默认值
|
|
337
|
+
const schema3 = {
|
|
338
|
+
path: '.config',
|
|
682
339
|
type: 'object',
|
|
683
|
-
properties: {
|
|
684
|
-
|
|
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
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
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
|
-
|
|
359
|
+
transform(schema4, { user: { name: null } });
|
|
360
|
+
// { user: { name: 'Guest' } }
|
|
701
361
|
```
|
|
702
362
|
|
|
703
363
|
## 许可证
|