Skip to content

Commit 68208ee

Browse files
committed
feat: 🎸 add v1 implementation
1 parent d7d52b9 commit 68208ee

File tree

8 files changed

+3353
-0
lines changed

8 files changed

+3353
-0
lines changed

.github/copilot-instructions.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
- Do not add trivial comments, usually do not add blank lines inside functions.
2+
- Use Angular style commits, e.g `feat: implemented xyz`.
3+
- Make sure tests (`yarn test`) pass.
4+
- In the end, make sure linter and formatter pass.

src/RandomJson.ts

Lines changed: 334 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,334 @@
1+
import {randomString, Token} from './string';
2+
3+
type JsonValue = unknown;
4+
5+
/** @ignore */
6+
export type NodeType = 'null' | 'boolean' | 'number' | 'string' | 'binary' | 'array' | 'object';
7+
8+
export interface NodeOdds {
9+
null: number;
10+
boolean: number;
11+
number: number;
12+
string: number;
13+
binary: number;
14+
array: number;
15+
object: number;
16+
}
17+
18+
export interface RandomJsonOptions {
19+
rootNode: 'object' | 'array' | 'string' | undefined;
20+
nodeCount: number;
21+
odds: NodeOdds;
22+
strings?: Token;
23+
}
24+
25+
const defaultOpts: RandomJsonOptions = {
26+
rootNode: 'object',
27+
nodeCount: 32,
28+
odds: {
29+
null: 1,
30+
boolean: 2,
31+
number: 10,
32+
string: 8,
33+
binary: 0,
34+
array: 2,
35+
object: 2,
36+
},
37+
};
38+
39+
type ContainerNode = unknown[] | object;
40+
41+
const ascii = (): string => {
42+
return String.fromCharCode(Math.floor(32 + Math.random() * (126 - 32)));
43+
};
44+
45+
const alphabet = [
46+
'a',
47+
'b',
48+
'c',
49+
'd',
50+
'e',
51+
'f',
52+
'g',
53+
'h',
54+
'i',
55+
'j',
56+
'k',
57+
'l',
58+
'm',
59+
'n',
60+
'o',
61+
'p',
62+
'q',
63+
'r',
64+
's',
65+
't',
66+
'u',
67+
'v',
68+
'w',
69+
'x',
70+
'y',
71+
'z',
72+
'A',
73+
'B',
74+
'C',
75+
'D',
76+
'E',
77+
'F',
78+
'G',
79+
'H',
80+
'I',
81+
'J',
82+
'K',
83+
'L',
84+
'M',
85+
'N',
86+
'O',
87+
'P',
88+
'Q',
89+
'R',
90+
'S',
91+
'T',
92+
'U',
93+
'V',
94+
'W',
95+
'X',
96+
'Y',
97+
'Z',
98+
'0',
99+
'1',
100+
'2',
101+
'3',
102+
'4',
103+
'5',
104+
'6',
105+
'7',
106+
'8',
107+
'9',
108+
'-',
109+
'_',
110+
'.',
111+
',',
112+
';',
113+
'!',
114+
'@',
115+
'#',
116+
'$',
117+
'%',
118+
'^',
119+
'&',
120+
'*',
121+
'\\',
122+
'/',
123+
'(',
124+
')',
125+
'+',
126+
'=',
127+
'\n',
128+
'👍',
129+
'🏻',
130+
'😛',
131+
'ä',
132+
'ö',
133+
'ü',
134+
'ß',
135+
'а',
136+
'б',
137+
'в',
138+
'г',
139+
'诶',
140+
'必',
141+
'西',
142+
];
143+
const utf16 = (): string => {
144+
return alphabet[Math.floor(Math.random() * alphabet.length)];
145+
};
146+
147+
/**
148+
* Create a random JSON value.
149+
*
150+
* ```ts
151+
* RandomJson.generate()
152+
* ```
153+
*/
154+
export class RandomJson {
155+
public static generate(opts?: Partial<RandomJsonOptions>): JsonValue {
156+
const rnd = new RandomJson(opts);
157+
return rnd.create();
158+
}
159+
160+
public static genBoolean(): boolean {
161+
return Math.random() > 0.5;
162+
}
163+
164+
public static genNumber(): number {
165+
const num =
166+
Math.random() > 0.2
167+
? Math.random() * 1e9
168+
: Math.random() < 0.2
169+
? Math.round(0xff * (2 * Math.random() - 1))
170+
: Math.random() < 0.2
171+
? Math.round(0xffff * (2 * Math.random() - 1))
172+
: Math.round(Number.MAX_SAFE_INTEGER * (2 * Math.random() - 1));
173+
if (num === -0) return 0;
174+
return num;
175+
}
176+
177+
public static genString(length = Math.ceil(Math.random() * 16)): string {
178+
let str: string = '';
179+
if (Math.random() < 0.1) for (let i = 0; i < length; i++) str += utf16();
180+
else for (let i = 0; i < length; i++) str += ascii();
181+
if (str.length !== length) return ascii().repeat(length);
182+
return str;
183+
}
184+
185+
public static genBinary(length = Math.ceil(Math.random() * 16)): Uint8Array {
186+
const buf = new Uint8Array(length);
187+
for (let i = 0; i < length; i++) buf[i] = Math.floor(Math.random() * 256);
188+
return buf;
189+
}
190+
191+
public static genArray(options: Partial<Omit<RandomJsonOptions, 'rootNode'>> = {odds: defaultOpts.odds}): unknown[] {
192+
return RandomJson.generate({
193+
nodeCount: 6,
194+
...options,
195+
rootNode: 'array',
196+
}) as unknown[];
197+
}
198+
199+
public static genObject(options: Partial<Omit<RandomJsonOptions, 'rootNode'>> = {odds: defaultOpts.odds}): object {
200+
return RandomJson.generate({
201+
nodeCount: 6,
202+
...options,
203+
rootNode: 'object',
204+
}) as object;
205+
}
206+
207+
/** @ignore */
208+
public opts: RandomJsonOptions;
209+
/** @ignore */
210+
private totalOdds: number;
211+
/** @ignore */
212+
private oddTotals: NodeOdds;
213+
/** @ignore */
214+
public root: JsonValue;
215+
/** @ignore */
216+
private containers: ContainerNode[] = [];
217+
218+
/**
219+
* @ignore
220+
*/
221+
public constructor(opts: Partial<RandomJsonOptions> = {}) {
222+
this.opts = {...defaultOpts, ...opts};
223+
this.oddTotals = {} as any;
224+
this.oddTotals.null = this.opts.odds.null;
225+
this.oddTotals.boolean = this.oddTotals.null + this.opts.odds.boolean;
226+
this.oddTotals.number = this.oddTotals.boolean + this.opts.odds.number;
227+
this.oddTotals.string = this.oddTotals.number + this.opts.odds.string;
228+
this.oddTotals.binary = this.oddTotals.string + this.opts.odds.binary;
229+
this.oddTotals.array = this.oddTotals.string + this.opts.odds.array;
230+
this.oddTotals.object = this.oddTotals.array + this.opts.odds.object;
231+
this.totalOdds =
232+
this.opts.odds.null +
233+
this.opts.odds.boolean +
234+
this.opts.odds.number +
235+
this.opts.odds.string +
236+
this.opts.odds.binary +
237+
this.opts.odds.array +
238+
this.opts.odds.object;
239+
if (this.opts.rootNode === 'string') {
240+
this.root = this.generateString();
241+
this.opts.nodeCount = 0;
242+
} else {
243+
this.root =
244+
this.opts.rootNode === 'object'
245+
? {}
246+
: this.opts.rootNode === 'array'
247+
? []
248+
: this.pickContainerType() === 'object'
249+
? {}
250+
: [];
251+
this.containers.push(this.root as ContainerNode);
252+
}
253+
}
254+
255+
/**
256+
* @ignore
257+
*/
258+
public create(): JsonValue {
259+
for (let i = 0; i < this.opts.nodeCount; i++) this.addNode();
260+
return this.root;
261+
}
262+
263+
/**
264+
* @ignore
265+
*/
266+
public addNode(): void {
267+
const container = this.pickContainer();
268+
const newNodeType = this.pickNodeType();
269+
const node = this.generate(newNodeType);
270+
if (node && typeof node === 'object') this.containers.push(node as any);
271+
if (Array.isArray(container)) {
272+
const index = Math.floor(Math.random() * (container.length + 1));
273+
container.splice(index, 0, node);
274+
} else {
275+
const key = RandomJson.genString();
276+
(container as any)[key] = node;
277+
}
278+
}
279+
280+
/**
281+
* @ignore
282+
*/
283+
protected generate(type: NodeType): unknown {
284+
switch (type) {
285+
case 'null':
286+
return null;
287+
case 'boolean':
288+
return RandomJson.genBoolean();
289+
case 'number':
290+
return RandomJson.genNumber();
291+
case 'string':
292+
return this.generateString();
293+
case 'binary':
294+
return RandomJson.genBinary();
295+
case 'array':
296+
return [];
297+
case 'object':
298+
return {};
299+
}
300+
}
301+
302+
protected generateString(): string {
303+
const strings = this.opts.strings;
304+
return strings ? randomString(strings) : RandomJson.genString();
305+
}
306+
307+
/** @ignore */
308+
public pickNodeType(): NodeType {
309+
const odd = Math.random() * this.totalOdds;
310+
if (odd <= this.oddTotals.null) return 'null';
311+
if (odd <= this.oddTotals.boolean) return 'boolean';
312+
if (odd <= this.oddTotals.number) return 'number';
313+
if (odd <= this.oddTotals.string) return 'string';
314+
if (odd <= this.oddTotals.binary) return 'binary';
315+
if (odd <= this.oddTotals.array) return 'array';
316+
return 'object';
317+
}
318+
319+
/**
320+
* @ignore
321+
*/
322+
protected pickContainerType(): 'array' | 'object' {
323+
const sum = this.opts.odds.array + this.opts.odds.object;
324+
if (Math.random() < this.opts.odds.array / sum) return 'array';
325+
return 'object';
326+
}
327+
328+
/**
329+
* @ignore
330+
*/
331+
protected pickContainer(): ContainerNode {
332+
return this.containers[Math.floor(Math.random() * this.containers.length)];
333+
}
334+
}

0 commit comments

Comments
 (0)