@zwa73/utils 1.0.90 → 1.0.93

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/src/UtilCodecs.ts CHANGED
@@ -1,4 +1,4 @@
1
- import * as he from 'html-entities';
1
+ import he from 'html-entities';
2
2
  import {get_encoding,Tiktoken} from 'tiktoken';
3
3
 
4
4
 
@@ -11,27 +11,11 @@ let encoderTurbo:Tiktoken|null = null;
11
11
  let encoderDavinci:Tiktoken|null = null;
12
12
  const textDecoder = new TextDecoder();
13
13
 
14
- // 定义一个对象,存储常见的HTML实体和对应的字符
15
- let htmlEntities:Record<string,string> = {
16
- "&lt;": "<",
17
- "&gt;": ">",
18
- "&amp;": "&",
19
- "&quot;": "\"",
20
- "&#39;": "'",
21
- "&#91;": "[",
22
- "&#93;": "]"
23
- };
24
-
25
14
  /**HTML实体解码 将一个字符串中的HTML实体转换为对应的字符
26
15
  * @param str - 要转换的字符串
27
16
  * @returns 转换后的字符串
28
17
  */
29
18
  export function decodeHtmlEntities(str:string) {
30
- //for(let code in htmlEntities){
31
- // let cha = htmlEntities[code]
32
- // str = str.replaceAll(code, cha);
33
- //}
34
- //return str
35
19
  return he.decode(str);
36
20
  }
37
21
 
@@ -40,15 +24,10 @@ export function decodeHtmlEntities(str:string) {
40
24
  * @returns 转换后的字符串
41
25
  */
42
26
  export function encodeHtmlEntities(str:string) {
43
- //for(let code in htmlEntities){
44
- // let cha = htmlEntities[code]
45
- // str = str.replaceAll(cha, code);
46
- //}
47
- //return str
48
27
  return he.encode(str);
49
28
  }
50
29
 
51
-
30
+ //#region LAM
52
31
  //token长度计算器
53
32
  //cl100k_base ChatGPT models, text-embedding-ada-002
54
33
  //p50k_base Code models, text-davinci-002, text-davinci-003
@@ -114,5 +93,5 @@ export function decodeTokenDavinci(arr:Uint32Array):string{
114
93
  initTikTokenEncoder();
115
94
  return textDecoder.decode(encoderDavinci?.decode(arr));
116
95
  }
117
-
96
+ //#endregion
118
97
  }
package/src/UtilFP.ts CHANGED
@@ -1,18 +1,21 @@
1
+ import { Keyable, LiteralCheck } from "./UtilInterfaces";
2
+ import { None } from "./UtilSymbol";
1
3
 
2
4
  /**常用函数式编程库 */
3
5
  export namespace UtilFP {
4
6
 
5
7
  /**柯里化函数类型 */
6
- type CurryFunc<T, PrevArgs extends unknown[] = []> = T extends (...args: infer Args) => infer Result
7
- ? Args extends [infer Arg, ...infer RestArgs]
8
- ? RestArgs extends []
9
- ? (...args: [...PrevArgs, Arg]) => Result
10
- : (...args: [...PrevArgs, Arg]) => CurryFunc<(...rest:RestArgs) => Result>
11
- & CurryFunc<(...args: RestArgs) => Result, [...PrevArgs, Arg]>
12
- : Args extends []
13
- ? () => Result
14
- : "CurryFunc错误 可选无法被识别" & Error
15
- : never;
8
+ type CurryFunc<T, PrevArgs extends unknown[] = []> =
9
+ T extends (...args: infer Args) => infer Result
10
+ ? Args extends [infer Arg, ...infer RestArgs]
11
+ ? RestArgs extends []
12
+ ? ((...args: [...PrevArgs, Arg]) => Result)
13
+ : ((...args: [...PrevArgs, Arg]) => CurryFunc<(...rest:RestArgs) => Result>)
14
+ & (CurryFunc<(...args: RestArgs) => Result, [...PrevArgs, Arg]>)
15
+ : Args extends []
16
+ ? () => Result
17
+ : "CurryFunc错误 默认参数可选无法被识别" & Error
18
+ : "CurryFunc错误 传入的并非函数" & Error;
16
19
 
17
20
  /**柯里化转换
18
21
  * @param {T} fn - 将要转换的函数
@@ -76,15 +79,88 @@ export function pipe<T>(input: T, ...fs: ((arg: T) => T)[]): T {
76
79
  }
77
80
 
78
81
 
79
- /**将一个字段加入一个对象中,返回新类型 */
80
- export function bindTo<K extends string, T, B extends {} = {}>
81
- (key:K,value:T,base?:B): B & Record<K,T>{
82
- let out = base as any;
83
- out = out??{};
84
- out[key]=value;
85
- return out;
82
+ /**将一个字段加入一个对象中, 返回新类型 */
83
+ //#region bind重载
84
+ /**绑定一个键和一个值到一个基础对象上, 并返回一个新的对象
85
+ * 新的对象包含了基础对象的所有属性, 以及一个新的属性,
86
+ * 这个新的属性的键是`key`, 值是`value`
87
+ * @template K - 要添加到对象的键的类型 必须为字面量
88
+ * @template V - 要添加到对象的值的类型
89
+ * @param key - 要添加到对象的键 必须为字面量
90
+ * @param value - 要添加到对象的值
91
+ * @returns 一个函数, 这个函数接受一个基础对象,
92
+ * 然后返回一个新的对象新的对象包含了基础对象的所有属性,
93
+ * 以及一个新的属性, 这个新的属性的键是`key`, 值是`value`
94
+ */
95
+ export function bind<K extends Keyable, V>
96
+ (key: LiteralCheck<K>,value: V): <B extends {}>
97
+ (base?: B) => keyof B extends K
98
+ ? "Base中已有对应键"&Error
99
+ : {[P in K | keyof B]: P extends K ? V : P extends keyof B ? B[P] : never;};
100
+ /**绑定一个键到一个基础对象上,
101
+ * 并返回一个新的对象新的对象包含了基础对象的所有属性,
102
+ * 以及一个新的属性, 这个新的属性的键是`key`, 值是基础对象
103
+ * @template K - 要添加到对象的键的类型 必须为字面量
104
+ * @param key - 要添加到对象的键 必须为字面量
105
+ * @returns 一个函数, 这个函数接受一个基础对象,
106
+ * 然后返回一个新的对象新的对象包含了基础对象的所有属性,
107
+ * 以及一个新的属性, 这个新的属性的键是`key`, 值是基础对象
108
+ */
109
+ export function bind<K extends Keyable>
110
+ (key: LiteralCheck<K>): <B extends {}>
111
+ (base?: B) => { [P in K]: B };
112
+ /**绑定一个键和一个值到一个基础对象上, 并返回一个新的对象
113
+ * 新的对象包含了基础对象的所有属性, 以及一个新的属性,
114
+ * 这个新的属性的键是`key`, 值是`value`
115
+ * @template K - 要添加到对象的键的类型 必须为字面量
116
+ * @template V - 要添加到对象的值的类型
117
+ * @template B - 基础对象的类型
118
+ * @param key - 要添加到对象的键 必须为字面量
119
+ * @param value - 要添加到对象的值
120
+ * @param base - 基础对象
121
+ * @returns 完成绑定的基础对象
122
+ */
123
+ export function bind<K extends Keyable, V, B extends {}>
124
+ (key: LiteralCheck<K>,value: V, base:B):keyof B extends K
125
+ ? "Base中已有对应键"&Error
126
+ : {[P in K | keyof B]: P extends K ? V : P extends keyof B ? B[P] : never;};
127
+ //#endregion
128
+ export function bind
129
+ <K extends Keyable, V, B>(key: LiteralCheck<K>, value?: V, base?:B){
130
+ if(base !== undefined)
131
+ return { ...base, [key as Keyable]: value };
132
+ if(value ===undefined)
133
+ return <B>(base?: B) =>
134
+ ({[key as Keyable]:base});
135
+
136
+ return <B extends {}>
137
+ (base?: B):{
138
+ [P in K | keyof B]: P extends K ? V : P extends keyof B ? B[P] : never;
139
+ } => base !== undefined
140
+ ? { ...base, [key as Keyable]: value }
141
+ : ({ [key as Keyable]: value } as any);
86
142
  }
87
143
 
144
+ /**创建一个对对象的每个属性进行映射的函数
145
+ * @param func - 映射函数, 接受一个属性值和它的键, 返回一个新的属性值
146
+ * @returns 一个函数, 这个函数接受一个对象, 然后返回一个新的对象新的对象的每个属性都是原始对象的属性经过映射函数处理后的结果
147
+ */
148
+ export function map<VAL, OUT, KEY extends symbol|string = string>
149
+ (func:(input:VAL,k:KEY)=>OUT):
150
+ <OM extends Record<KEY,VAL>>
151
+ (input:OM)=>keyof OM extends KEY
152
+ ? {[P in keyof OM]:OUT}
153
+ : "map函数传入的键类型无法处理此对象"&Error{
154
+ return (input) => {
155
+ return Reflect
156
+ .ownKeys(input as Record<Keyable,unknown>)
157
+ .reduce((sum,curr)=>({ ...sum,
158
+ [curr] : func((input as any)[curr],curr as any)
159
+ }),{}) as any;
160
+ };
161
+ }
162
+
163
+
88
164
  /**
89
165
  let asd = bindTo("sss",123,{abc:223});//?
90
166
  let sumvoid = ()=>10;
@@ -99,5 +175,11 @@ let sumz = sumCu(1,"b",3)(false);//?
99
175
  console.log(suma(2,true));
100
176
  console.log(sumb(true));
101
177
  console.log(sumc("s",3,false));
102
- */
178
+
179
+ Reflect.ownKeys({[1]:1,[None]:2});//?
180
+ let as = "ssa" as string;//?
181
+ let asv = {"s":1};//?
182
+ let a = map((v:number,k)=>v*21);//?
183
+ let b = a({["ss"]:1})//?
184
+ */
103
185
  }
@@ -1,9 +1,9 @@
1
- import { FfmpegCommand, FfprobeData } from "fluent-ffmpeg";
2
- import * as fluentFfmpeg from "fluent-ffmpeg";
1
+ import { FfprobeData } from "fluent-ffmpeg";
2
+ import fluentFfmpeg from "fluent-ffmpeg";
3
3
  import * as path from "path";
4
4
  import * as fs from "fs";
5
- import { SList, SStream } from "@src/UtilClass";
6
5
  import { SLogger } from "@src/UtilLogger";
6
+ import { Stream } from "./UtilClass";
7
7
 
8
8
  /**输入输出路径映射
9
9
  * 输入路径:输入路径
@@ -13,8 +13,7 @@ export type IOMap = { [key: string]: string };
13
13
  /**ffmpeg工具类
14
14
  */
15
15
  class SFfmpegTool {
16
- /**静态构造函数
17
- */
16
+ /**静态构造函数 */
18
17
  static init() {
19
18
  let ffmpegPath = process.env.FFMPEG_PATH;
20
19
  if(ffmpegPath!=null){
@@ -22,8 +21,7 @@ class SFfmpegTool {
22
21
  SFfmpegTool.setFfmpegPath(exepath);
23
22
  }
24
23
  }
25
- /**设置ffmpeg路径
26
- */
24
+ /**设置ffmpeg路径 */
27
25
  static setFfmpegPath(ffmpegPath: string) {
28
26
  fluentFfmpeg.setFfmpegPath(ffmpegPath);
29
27
  }
@@ -168,13 +166,12 @@ class SFfmpegTool {
168
166
  * @param cpCount - 并发数
169
167
  */
170
168
  static async wav2oggMP(ioMap: IOMap, quality = 10, cpCount = 16) {
171
- await new SList(Object.entries(ioMap))
172
- .toSStream(cpCount)
169
+ await new Stream(Object.entries(ioMap))
173
170
  .map(async ([inPath, outPath]) => {
174
171
  SLogger.info("SFfmpegTool.wav2oggMP 正在处理:" + outPath);
175
172
  await SFfmpegTool.wav2ogg(inPath, outPath, quality);
176
173
  })
177
- .appendOperations();
174
+ .append();
178
175
  }
179
176
  /**flac转ogg多线程
180
177
  * @param ioMap - 输入输出路径映射
@@ -186,13 +183,12 @@ class SFfmpegTool {
186
183
  quality: number = 10,
187
184
  cpCount: number = 16
188
185
  ) {
189
- await new SList(Object.entries(ioMap))
190
- .toSStream(cpCount)
186
+ await new Stream(Object.entries(ioMap))
191
187
  .map(async ([inPath, outPath]) => {
192
188
  SLogger.info("SFfmpegTool.flac2oggMP 正在处理:" + outPath);
193
189
  await SFfmpegTool.flac2ogg(inPath, outPath, quality);
194
190
  })
195
- .appendOperations();
191
+ .append();
196
192
  }
197
193
  /**删除静音多线程
198
194
  * @param ioMap - 输入输出路径映射
@@ -205,13 +201,12 @@ class SFfmpegTool {
205
201
  silence: number = 0.2,
206
202
  cpCount: number = 16
207
203
  ) {
208
- await new SList(Object.entries(ioMap))
209
- .toSStream(cpCount)
204
+ await new Stream(Object.entries(ioMap))
210
205
  .map(async ([inPath, outPath]) => {
211
206
  SLogger.info("SFfmpegTool.trimSilenceMP 正在处理:" + outPath);
212
207
  await SFfmpegTool.trimSilence(inPath, outPath, threshold, silence);
213
208
  })
214
- .appendOperations();
209
+ .append();
215
210
  }
216
211
 
217
212
  /**重采样多线程
@@ -220,13 +215,12 @@ class SFfmpegTool {
220
215
  * @param cpCount - 并发数
221
216
  */
222
217
  static async resampleMP(ioMap: IOMap, rate: number = 22050, cpCount: number = 16) {
223
- await new SList(Object.entries(ioMap))
224
- .toSStream(cpCount)
218
+ await new Stream(Object.entries(ioMap))
225
219
  .map(async ([inPath, outPath]) => {
226
220
  SLogger.info("SFfmpegTool.resampleMP 正在处理:" + outPath);
227
221
  await SFfmpegTool.resample(inPath, outPath, rate);
228
222
  })
229
- .appendOperations();
223
+ .append();
230
224
  }
231
225
  }
232
226
  SFfmpegTool.init();
@@ -3,7 +3,7 @@ import { AnyFunc, ComposedClass, ComposedMixinable, ExtractOutcome, IJData, JObj
3
3
  import * as cp from "child_process";
4
4
  import { SLogger } from "@src/UtilLogger";
5
5
  import { Completed, Failed, FailedLike, None, StatusSymbol, Success, SuccessLike, Terminated, Timeout } from "./UtilSymbol";
6
- import { DeferAsync, LogTimeAsync } from "./UtilDecorators";
6
+ import { Catch, DeferAsync, LogTimeAsync } from "./UtilDecorators";
7
7
 
8
8
 
9
9
  type SuccessOut<T> = Outcome<Success,T>;
@@ -356,30 +356,47 @@ static pendingMap:Record<Keyable,AnyFunc[]> = {};
356
356
  * @param task - 任务逻辑
357
357
  * @returns 处理结果
358
358
  */
359
- static async queueProc<T>(flag:Keyable,task:(()=>Promise<T>)):Promise<T> {
360
- this.pendingMap[flag] = this.pendingMap[flag]??[];
361
- const pending = this.pendingMap[flag];
362
-
363
- //尝试解除下个任务的等待
364
- const tryRes = () => {
365
- const thispd = pending;
366
- const resolve = thispd.shift();
367
- if(resolve) resolve();
368
- }
359
+ static queueProc<T>(flag: Keyable, task: () => Promise<T>): Promise<T> {
360
+ // 如果当前标签的队列不存在,则创建一个新的队列
361
+ if (!this.pendingMap[flag]) this.pendingMap[flag] = [];
362
+
363
+ // 创建一个新的Promise,并保存resolve函数以便后续调用
364
+ let resolveFunc: (value: T | PromiseLike<T>) => void;
365
+ const promise = new Promise<T>((resolve) => {
366
+ resolveFunc = resolve;
367
+ });
369
368
 
370
- //空时直接运行 非空时等待
371
- if (pending.length <= 0)
372
- pending.push(()=>tryRes());
373
- else await new Promise((resolve) => pending.push(resolve));
374
-
375
- try {
376
- return await task();;
377
- } catch(e){
378
- SLogger.fatal(`queuePromise 出现错误`,e);
379
- throw e;
380
- }finally {
381
- tryRes();
382
- }
369
+ // 定义处理任务的函数
370
+ const processTask = async () => {
371
+ let result: T;
372
+ try {
373
+ // 执行任务并等待结果
374
+ result = await task();
375
+ // 使用保存的resolve函数来解决Promise
376
+ resolveFunc(result);
377
+ } catch (error) {
378
+ // 如果任务执行出错,记录错误日志
379
+ SLogger.warn(`queueProc 错误: `,error,`flag: ${String(flag)}`);
380
+ } finally {
381
+ // 无论任务是否成功,都从队列中移除当前任务
382
+ this.pendingMap[flag].shift();
383
+ // 如果队列中还有任务,执行下一个任务
384
+ if (this.pendingMap[flag].length > 0) {
385
+ this.pendingMap[flag][0]();
386
+ } else {
387
+ // 如果队列中没有任务,删除队列
388
+ delete this.pendingMap[flag];
389
+ }
390
+ }
391
+ };
392
+
393
+ // 将处理任务的函数添加到队列中
394
+ this.pendingMap[flag].push(processTask);
395
+ // 如果队列中只有当前任务,立即执行
396
+ if (this.pendingMap[flag].length === 1) processTask();
397
+
398
+ // 返回Promise,以便调用者可以等待任务完成
399
+ return promise;
383
400
  }
384
401
  /**队列获取目标的代办事件数
385
402
  * @param flag - 队列标签
@@ -417,15 +434,13 @@ static matchProc<
417
434
  if (typeof t === 'string' || typeof t === 'number' || typeof t === 'symbol'){
418
435
  if ((procObj as any)[t])
419
436
  return (procObj as any)[t](t);
420
- SLogger.fatal(`matchProc 传入了一个预料之外的值: `,t);
421
- throw None;
422
- }
423
- else{
424
- if ((procObj as any)[t.status])
425
- return (procObj as any)[t.status](t.status,(t as any).result);
426
- SLogger.fatal(`matchProc 传入了一个预料之外的值: `,t);
427
- throw None;
428
437
  }
438
+ else if ((procObj as any)[t.status])
439
+ return (procObj as any)[t.status](t.status,(t as any).result);
440
+
441
+ const err = new Error(`matchProc 传入了一个预料之外的值: ${String(t)}`);
442
+ SLogger.fatal(err);
443
+ throw err;
429
444
  }
430
445
 
431
446
  /**根据典型的成功或失败状态运行函数
@@ -464,7 +479,7 @@ static isFailed<T>
464
479
  /**是成功的 */
465
480
  static isSuccess<T>
466
481
  (tg:T):T extends Matchable<SuccessLike>
467
- ? true: MatchableFlag<T> extends Exclude<MatchableFlag<T>,SuccessLike>
482
+ ? true : MatchableFlag<T> extends Exclude<MatchableFlag<T>,SuccessLike>
468
483
  ? false : boolean{
469
484
  const test = (t:any)=>t === Success || t === Completed;
470
485
  if(tg!=null && typeof tg === 'object' && 'status' in tg)
@@ -487,6 +502,17 @@ static likeNone(t:unknown,strictLog=true):t is None{
487
502
  return t == null;
488
503
  }
489
504
 
505
+ /**验证一个值是否为空
506
+ * 为空则抛异
507
+ * @param t - 验证目标
508
+ * @param errLog - 异常信息
509
+ * @returns 排除None的原值
510
+ */
511
+ static expect<T>(t:T,errLog?:string):Exclude<T,None>{
512
+ if(t===None) throw new Error(errLog);
513
+ return t as Exclude<T,None>;
514
+ }
515
+
490
516
  /**验证一个变量的类型是否为 T
491
517
  * @template T - 验证类型
492
518
  * @param t - 验证目标
@@ -523,4 +549,15 @@ static isSafeNumber(num: unknown): boolean {
523
549
  return false;
524
550
  }
525
551
 
552
+ /**将一个字段加入一个对象中,将改变对象,返回新类型 */
553
+ static bindTo<K extends Keyable, V, B extends {} = {}>
554
+ (key: LiteralCheck<K>,value: V, base?:B):keyof B extends K
555
+ ? "Base中已有对应键"&Error
556
+ : {[P in K | keyof B]: P extends K ? V : P extends keyof B ? B[P] : never;}{
557
+ let out = base as any;
558
+ out = out??{};
559
+ out[key]=value;
560
+ return out;
561
+ }
562
+
526
563
  }
@@ -45,6 +45,11 @@ export type LiteralCheck<L> =
45
45
  ProperSubset<Record<any,any>, L> |
46
46
  null | undefined;
47
47
 
48
+ /**返回将Mixin分配给Base的结果,相同字段时Mixin覆盖Base */
49
+ export type AssignObject<Base extends {}, Mixin extends {}> = {
50
+ [P in keyof Mixin | keyof Base]: P extends keyof Mixin ? Mixin[P] : P extends keyof Base ? Base[P] : never;
51
+ }
52
+
48
53
  /**转为可写的 */
49
54
  export type Writeable<T> = {
50
55
  -readonly [P in keyof T]: T[P]
package/src/UtilLogger.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as path from 'path';
2
2
  import * as winston from 'winston';
3
- import * as DailyRotateFile from 'winston-daily-rotate-file';
3
+ import DailyRotateFile from 'winston-daily-rotate-file';
4
4
  import {inspect} from 'util';
5
5
 
6
6
 
@@ -253,17 +253,16 @@ export class SLogger{
253
253
  let acc = 0;
254
254
  const maxAcc = 3;
255
255
 
256
- let concat = function(num:number,sep:string,
257
- formatText:string,resultText:string):void{
258
- let hasResult = result.length>0;
259
- let need = (hasResult || num>0) && (acc < maxAcc);
260
- if(need){
261
- if(result.length>0) result+=sep;
262
- if(format.length>0) format+=sep;
263
- result += resultText;
264
- format += formatText;
265
- acc++;
266
- }
256
+ const concat = (num:number,sep:string,
257
+ formatText:string,resultText:string): void => {
258
+ const hasResult = result.length>0;
259
+ const need = (hasResult || num>0) && (acc < maxAcc);
260
+ if(!need) return;
261
+ if(result.length>0) result+=sep;
262
+ if(format.length>0) format+=sep;
263
+ result += resultText;
264
+ format += formatText;
265
+ acc++;
267
266
  }
268
267
  concat(Days,':','dd',
269
268
  `${Days.toString()}`);
@@ -285,7 +284,7 @@ export class SLogger{
285
284
  }
286
285
 
287
286
  if(level!==null)
288
- this.log(level,flag+": "+out);
287
+ this.log(level,`${flag}: ${out}`);
289
288
  delete SLogger.timeTable[flag];
290
289
  return out;
291
290
  }
@@ -301,7 +300,7 @@ export class SLogger{
301
300
  }
302
301
  /**让名称为default的logger 产生一条对应等级的log 返回自身
303
302
  * @param level - log等级
304
- * @param messages - log消息
303
+ * @param messages - log消息
305
304
  * @returns 自身
306
305
  */
307
306
  static log(level: LogLevel, ...messages:Array<any>):SLogger{
package/tsconfig.json CHANGED
@@ -3,7 +3,9 @@
3
3
  "allowJs": true,
4
4
  "strict": true,
5
5
  "target": "ES2022",
6
- "module": "commonjs",
6
+ "module": "CommonJS",
7
+ "moduleResolution": "node",
8
+ "esModuleInterop": true,
7
9
  "outDir": "./dist",
8
10
  "declaration": true,
9
11
  "baseUrl": ".",
@@ -1,4 +0,0 @@
1
- {
2
- "$schema": "http://json-schema.org/draft-07/schema#",
3
- "$ref": "schemas.json#/definitions/TestType1"
4
- }
@@ -1,4 +0,0 @@
1
- {
2
- "$schema": "http://json-schema.org/draft-07/schema#",
3
- "$ref": "schemas.json#/definitions/TestType2"
4
- }
@@ -1,35 +0,0 @@
1
- {
2
- "$schema": "http://json-schema.org/draft-07/schema#",
3
- "definitions": {
4
- "TestType2": {
5
- "type": "object",
6
- "properties": {
7
- "a": {
8
- "type": "string"
9
- },
10
- "b": {
11
- "type": "number"
12
- }
13
- },
14
- "required": [
15
- "a",
16
- "b"
17
- ]
18
- },
19
- "TestType1": {
20
- "type": "object",
21
- "properties": {
22
- "a": {
23
- "type": "string"
24
- },
25
- "b": {
26
- "type": "number"
27
- }
28
- },
29
- "required": [
30
- "a",
31
- "b"
32
- ]
33
- }
34
- }
35
- }