Skip to content

Commit 49e341a

Browse files
authored
Merge pull request #1 from jsonjoy-com/structured-random
Structured random
2 parents 68208ee + dad3f7d commit 49e341a

File tree

17 files changed

+1101
-46
lines changed

17 files changed

+1101
-46
lines changed

README.md

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
1-
# @jsonjoy.com/json-random
1+
# `json-random`
2+
3+
The `json-random` library lets you generate random JSON values.
4+
5+
- `randomString` - generates a random string following a string token template.
6+
- `TemplateJson` - generates random JSON following a template.
7+
- `RandomJson` - generates random JSON by generating new nodes and inserting them into random positions in the JSON.
8+
- `int` - generates a random integer.
9+
- `deterministic(seed, () => {})` - fixates `Math.random()` such that code in callback generates a deterministic value.
210

311

412
## Use Cases
@@ -25,12 +33,18 @@ const optimizedFunction = codegen.compile();
2533
```
2634

2735

28-
# json-random
36+
## Reference
2937

30-
The `json-random` library lets you generate random JSON values.
38+
### `randomString`
39+
40+
TODO: ...
41+
42+
43+
### `TemplateJson`
3144

45+
TODO: ...
3246

33-
## Usage
47+
### `RandomJson`
3448

3549
Generate a random JSON object.
3650

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,9 @@
5454
"peerDependencies": {
5555
"tslib": "2"
5656
},
57-
"dependencies": {},
57+
"dependencies": {
58+
"@jsonjoy.com/buffers": "^1.0.0"
59+
},
5860
"devDependencies": {
5961
"@biomejs/biome": "^1.9.4",
6062
"@types/benchmark": "^2.1.2",

src/RandomJson.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {randomString, Token} from './string';
1+
import {randomString, type Token} from './string';
22

33
type JsonValue = unknown;
44

@@ -170,7 +170,7 @@ export class RandomJson {
170170
: Math.random() < 0.2
171171
? Math.round(0xffff * (2 * Math.random() - 1))
172172
: Math.round(Number.MAX_SAFE_INTEGER * (2 * Math.random() - 1));
173-
if (num === -0) return 0;
173+
if (num === 0) return 0;
174174
return num;
175175
}
176176

src/__demos__/map-demo.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/**
2+
* Run with:
3+
*
4+
* npx ts-node src/__demos__/map-demo.ts
5+
*/
6+
7+
import {TemplateJson} from '../structured/TemplateJson';
8+
9+
console.log('=== Map Template Demo ===\n');
10+
11+
// Basic map usage
12+
console.log('1. Basic map with shorthand:');
13+
const basicMap = TemplateJson.gen('map');
14+
console.log(JSON.stringify(basicMap, null, 2));
15+
16+
// Map with custom key tokens and values
17+
console.log('\n2. Map with custom user IDs and profile data:');
18+
const userMap = TemplateJson.gen([
19+
'map',
20+
['list', 'user_', ['pick', '001', '002', '003', '004', '005']],
21+
[
22+
'obj',
23+
[
24+
['name', ['str', ['list', ['pick', 'John', 'Jane', 'Bob', 'Alice'], ' ', ['pick', 'Doe', 'Smith', 'Johnson']]]],
25+
['age', ['int', 18, 65]],
26+
['active', 'bool'],
27+
],
28+
],
29+
2,
30+
4,
31+
]);
32+
console.log(JSON.stringify(userMap, null, 2));
33+
34+
// Map with complex nested structures
35+
console.log('\n3. Map with API endpoints and their configurations:');
36+
const apiMap = TemplateJson.gen([
37+
'map',
38+
['list', 'api/', ['pick', 'users', 'posts', 'comments', 'auth']],
39+
[
40+
'obj',
41+
[
42+
['method', ['str', ['pick', 'GET', 'POST', 'PUT', 'DELETE']]],
43+
['timeout', ['int', 1000, 5000]],
44+
['retries', ['int', 0, 3]],
45+
['auth_required', 'bool'],
46+
],
47+
],
48+
3,
49+
3,
50+
]);
51+
console.log(JSON.stringify(apiMap, null, 2));
52+
53+
// Map with guaranteed size
54+
console.log('\n4. Map with exactly 2 entries:');
55+
const fixedMap = TemplateJson.gen(['map', ['pick', 'key1', 'key2', 'key3'], ['or', 'str', 'int', 'bool'], 2, 2]);
56+
console.log(JSON.stringify(fixedMap, null, 2));

src/__tests__/RandomJson.spec.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -124,13 +124,7 @@ test('random strings can be converted to UTF-8', () => {
124124
test('can specify string generation schema', () => {
125125
const str = RandomJson.generate({
126126
rootNode: 'string',
127-
strings: [
128-
'list',
129-
[
130-
['repeat', 2, 2, 'xx'],
131-
['pick', ['y']],
132-
],
133-
],
127+
strings: ['list', ['repeat', 2, 2, 'xx'], ['pick', 'y']],
134128
});
135129
expect(str).toBe('xxxxy');
136130
});

src/__tests__/setup.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export const resetMathRandom = (seed = 123456789) => {
2+
Math.random = () => {
3+
seed = (seed * 48271) % 2147483647;
4+
return (seed - 1) / 2147483646;
5+
};
6+
};

src/__tests__/string.spec.ts

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,50 @@
1-
import {randomString, Token} from '../string';
1+
import {randomString, type Token} from '../string';
2+
import {deterministic} from '../util';
23

3-
// Tests for randomString
44
describe('randomString', () => {
55
it('should pick a random string from the array', () => {
6-
const token: Token = ['pick', ['apple', 'banana', 'cherry']];
6+
const token: Token = ['pick', 'apple', 'banana', 'cherry'];
77
const result = randomString(token);
88
expect(['apple', 'banana', 'cherry']).toContain(result);
99
});
1010

1111
it('should repeat a pattern a random number of times', () => {
12-
const token: Token = ['repeat', 2, 5, ['pick', ['x', 'y', 'z', ' ']]];
12+
const token: Token = ['repeat', 2, 5, ['pick', 'x', 'y', 'z', ' ']];
1313
const result = randomString(token);
1414
expect(result.length).toBeGreaterThanOrEqual(2);
1515
expect(result.length).toBeLessThanOrEqual(5);
1616
});
1717

1818
it('should pick a random character from the Unicode range', () => {
19-
const token: Token = ['range', 65, 90]; // A-Z
19+
const token: Token = ['char', 65, 90]; // A-Z
2020
const result = randomString(token);
2121
expect(result).toMatch(/^[A-Z]$/);
2222
});
2323

24-
// test tlist token
24+
it('should pick a random character from the Unicode range three times', () => {
25+
const token: Token = ['char', 65, 90, 3]; // A-Z
26+
const result = randomString(token);
27+
expect(result).toMatch(/^[A-Z]{3}$/);
28+
});
29+
2530
it('executes a list of tokens', () => {
2631
const token: Token = [
2732
'list',
28-
[
29-
['pick', ['monkey', 'dog', 'cat']],
30-
['pick', [' ']],
31-
['pick', ['ate', 'threw', 'picked']],
32-
['pick', [' ']],
33-
['pick', ['apple', 'banana', 'cherry']],
34-
],
33+
['pick', 'monkey', 'dog', 'cat'],
34+
['pick', ' '],
35+
['pick', 'ate', 'threw', 'picked'],
36+
['pick', ' '],
37+
['pick', 'apple', 'banana', 'cherry'],
3538
];
3639
const result = randomString(token);
3740
expect(/monkey|dog|cat/.test(result)).toBe(true);
3841
expect(/ate|threw|picked/.test(result)).toBe(true);
3942
expect(/apple|banana|cherry/.test(result)).toBe(true);
4043
});
44+
45+
it('can nest picks', () => {
46+
const token: Token = ['pick', ['pick', 'monkey', 'dog', 'cat'], ['pick', 'banana', 'apple']];
47+
const str = deterministic(123, () => randomString(token));
48+
expect(str).toBe('dog');
49+
});
4150
});

src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
1+
export {deterministic, rnd} from './util';
12
export * from './RandomJson';
3+
export * from './number';
24
export * from './string';
5+
export * from './structured';

src/number.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export const int = (min: number, max: number): number => {
2+
let int = Math.round(Math.random() * (max - min) + min);
3+
int = Math.max(min, Math.min(max, int));
4+
return int;
5+
};

src/string.ts

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,17 @@
11
/**
22
* Tokens used to specify random string generation options
33
*/
4-
export type Token = TokenLiteral | TokenPick | TokenRepeat | TokenRange | TokenList;
4+
export type Token = TokenLiteral | TokenPick | TokenRepeat | TokenChar | TokenList;
55

66
/**
77
* A string literal to use as-is.
88
*/
99
export type TokenLiteral = string;
1010

1111
/**
12-
* Picks a random string from the provided array of strings.
13-
* The `from` array can contain any number of strings.
12+
* Picks a random token from the provided tokens.
1413
*/
15-
export type TokenPick = [type: 'pick', from: string[]];
14+
export type TokenPick = [type: 'pick', ...from: Token[]];
1615

1716
/**
1817
* Repeats `pattern` a random number of times between `min` and `max`.
@@ -21,13 +20,14 @@ export type TokenRepeat = [type: 'repeat', min: number, max: number, pattern: To
2120

2221
/**
2322
* Specifies a Unicode code point range from which to pick a random character.
23+
* The `count` parameter specifies how many characters to pick, defaults to 1.
2424
*/
25-
export type TokenRange = [type: 'range', min: number, max: number];
25+
export type TokenChar = [type: 'char', min: number, max: number, count?: number];
2626

2727
/**
28-
* Executes a list of `what` tokens in sequence.
28+
* Executes a list of `every` tokens in sequence.
2929
*/
30-
export type TokenList = [type: 'list', what: Token[]];
30+
export type TokenList = [type: 'list', ...every: Token[]];
3131

3232
/**
3333
* Generates a random string based on the provided token.
@@ -39,26 +39,29 @@ export function randomString(token: Token): string {
3939
const rnd = Math.random();
4040
switch (token[0]) {
4141
case 'pick': {
42-
const set = token[1];
43-
return set[Math.floor(rnd * set.length)];
42+
const [, ...from] = token;
43+
return randomString(from[Math.floor(rnd * from.length)]);
4444
}
4545
case 'repeat': {
46-
const min = token[1];
47-
const max = token[2];
48-
const pattern = token[3];
46+
const [, min, max, pattern] = token;
4947
const count = Math.floor(rnd * (max - min + 1)) + min;
5048
let str = '';
5149
for (let i = 0; i < count; i++) str += randomString(pattern);
5250
return str;
5351
}
54-
case 'range': {
55-
const min = token[1];
56-
const max = token[2];
57-
const codePoint = Math.floor(rnd * (max - min + 1)) + min;
58-
return String.fromCodePoint(codePoint);
52+
case 'char': {
53+
const [, min, max, count = 1] = token;
54+
let str = '';
55+
for (let i = 0; i < count; i++) {
56+
const codePoint = Math.floor(rnd * (max - min + 1)) + min;
57+
str += String.fromCodePoint(codePoint);
58+
}
59+
return str;
60+
}
61+
case 'list': {
62+
const [, ...every] = token;
63+
return every.map(randomString).join('');
5964
}
60-
case 'list':
61-
return token[1].map(randomString).join('');
6265
default:
6366
throw new Error('Invalid token type');
6467
}

0 commit comments

Comments
 (0)