Skip to content

Commit 9eecddd

Browse files
committed
feat: 🎸 add JSON Type implementation
1 parent bf7423c commit 9eecddd

File tree

98 files changed

+13178
-2
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

98 files changed

+13178
-2
lines changed

README.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,42 @@ Features of the library:
1515
- Some discriminators are automatically inferred, when const types are used.
1616
- Custom validation rules can be added using the JSON Expression language.
1717
- Can generate random JSON values that match the schema.
18+
19+
20+
## Usage
21+
22+
Type builder for JSON-like data structures:
23+
24+
```ts
25+
import {t} from '@jsonjoy.com/json-type';
26+
27+
t.String(); // { kind: 'str' }
28+
t.String({const: 'add'}); // { kind: 'str', const: 'add' }
29+
30+
const type = t.Object([
31+
t.Field(
32+
'collection',
33+
t.Object([
34+
t.Field('id', t.String({format: 'ascii', noJsonEscape: true})),
35+
t.Field('ts', t.num, {format: 'u64'}),
36+
t.Field('cid', t.String({format: 'ascii', noJsonEscape: true})),
37+
t.Field('prid', t.String({format: 'ascii', noJsonEscape: true})),
38+
t.Field('slug', t.String({format: 'ascii', noJsonEscape: true})),
39+
t.Field('name', t.str, {isOptional: true}),
40+
t.Field('src', t.str, {isOptional: true}),
41+
t.Field('doc', t.str, {isOptional: true}),
42+
t.Field('authz', t.str, {isOptional: true}),
43+
t.Field('active', t.bool),
44+
]),
45+
),
46+
t.Field(
47+
'block',
48+
t.Object([
49+
t.Field('id', t.String({format: 'ascii', noJsonEscape: true})),
50+
t.Field('ts', t.num, {format: 'u64'}),
51+
t.Field('cid', t.String({format: 'ascii', noJsonEscape: true})),
52+
t.Field('slug', t.String({format: 'ascii', noJsonEscape: true})),
53+
]),
54+
),
55+
]);
56+
```

package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,21 +52,24 @@
5252
"publish-coverage-and-typedocs": "yarn typedoc && yarn coverage && yarn build:pages && yarn deploy:pages"
5353
},
5454
"peerDependencies": {
55+
"rxjs": "*",
5556
"tslib": "2"
5657
},
5758
"dependencies": {
58-
"@jsonjoy.com/util": "^1.3.0",
59-
"@jsonjoy.com/json-pack": "^1.0.0",
6059
"@jsonjoy.com/json-expression": "^1.0.0",
60+
"@jsonjoy.com/json-pack": "^1.1.0",
61+
"@jsonjoy.com/util": "^1.4.0",
6162
"sonic-forest": "^1.0.3",
6263
"tree-dump": "^1.0.2"
6364
},
6465
"devDependencies": {
6566
"@biomejs/biome": "^1.9.3",
6667
"@types/benchmark": "^2.1.5",
68+
"@types/node": "^22.7.4",
6769
"@vitest/coverage-v8": "^2.1.2",
6870
"benchmark": "^2.1.4",
6971
"config-galore": "^1.0.0",
72+
"rxjs": "^7.8.1",
7073
"tslib": "^2.7.0",
7174
"typescript": "^5.6.2",
7275
"vitest": "^2.1.2"

src/__bench__/encode.ts

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/* tslint:disable no-console */
2+
3+
import {TypeSystem} from '..';
4+
import {CborEncoder} from '@jsonjoy.com/json-pack/lib/cbor/CborEncoder';
5+
import {JsonEncoder} from '@jsonjoy.com/json-pack/lib/json/JsonEncoder';
6+
import {CompiledBinaryEncoder} from '../codegen/types';
7+
import {EncodingFormat} from '@jsonjoy.com/json-pack/lib/constants';
8+
import {Writer} from '@jsonjoy.com/util/lib/buffers/Writer';
9+
10+
const system = new TypeSystem();
11+
const {t} = system;
12+
13+
const response = system.alias(
14+
'Response',
15+
t.Object(
16+
t.prop(
17+
'collection',
18+
t.Object(
19+
t.prop('id', t.String({ascii: true, noJsonEscape: true})),
20+
t.prop('ts', t.num.options({format: 'u64'})),
21+
t.prop('cid', t.String({ascii: true, noJsonEscape: true})),
22+
t.prop('prid', t.String({ascii: true, noJsonEscape: true})),
23+
t.prop('slug', t.String({ascii: true, noJsonEscape: true})),
24+
t.propOpt('name', t.str),
25+
t.propOpt('src', t.str),
26+
t.propOpt('doc', t.str),
27+
t.propOpt('longText', t.str),
28+
t.prop('active', t.bool),
29+
t.prop('views', t.Array(t.num)),
30+
),
31+
),
32+
t.prop(
33+
'block',
34+
t.Object(
35+
t.prop('id', t.String({ascii: true, noJsonEscape: true})),
36+
t.prop('ts', t.num.options({format: 'u64'})),
37+
t.prop('cid', t.String({ascii: true, noJsonEscape: true})),
38+
t.prop('slug', t.String({ascii: true, noJsonEscape: true})),
39+
),
40+
),
41+
),
42+
);
43+
44+
const json = {
45+
collection: {
46+
id: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
47+
ts: Date.now(),
48+
cid: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
49+
prid: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
50+
slug: 'slug-name',
51+
name: 'Super collection',
52+
src: '{"foo": "bar"}',
53+
longText:
54+
'After implementing a workaround for the first issue and merging the changes to another feature branch with some extra code and tests, the following error was printed in the stage’s log “JavaScript heap out of memory error.”',
55+
active: true,
56+
views: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
57+
},
58+
block: {
59+
id: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
60+
ts: Date.now(),
61+
cid: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
62+
slug: 'slug-name',
63+
},
64+
};
65+
66+
const jsonTextEncoder = response.type.jsonTextEncoder();
67+
const jsonEncoderFn = response.type.encoder(EncodingFormat.Json) as CompiledBinaryEncoder;
68+
const cborEncoderFn = response.type.encoder(EncodingFormat.Cbor) as CompiledBinaryEncoder;
69+
70+
const jsonEncoder = new JsonEncoder(new Writer());
71+
const cborEncoder = new CborEncoder();
72+
73+
const {Suite} = require('benchmark');
74+
const suite = new Suite();
75+
suite
76+
.add(`json-type "json" text encoder and Buffer.from()`, () => {
77+
Buffer.from(jsonTextEncoder(json));
78+
})
79+
.add(`json-type "json" encoder`, () => {
80+
jsonEncoderFn(json, jsonEncoder);
81+
jsonEncoder.writer.flush();
82+
})
83+
.add(`json-type "cbor" encoder`, () => {
84+
cborEncoderFn(json, cborEncoder);
85+
cborEncoder.writer.flush();
86+
})
87+
.add(`json-pack CborEncoder`, () => {
88+
cborEncoder.encode(json);
89+
})
90+
.add(`Buffer.from(JSON.stringify())`, () => {
91+
Buffer.from(JSON.stringify(json));
92+
})
93+
.on('cycle', (event: any) => {
94+
console.log(String(event.target) + `, ${Math.round(1000000000 / event.target.hz)} ns/op`);
95+
})
96+
.on('complete', () => {
97+
console.log('Fastest is ' + suite.filter('fastest').map('name'));
98+
})
99+
.run();
100+
101+
// console.log(response.encoder('json').toString());
102+
// console.log(response.encoder('cbor').toString());

src/__demos__/json-type.ts

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
/**
2+
* Run with:
3+
*
4+
* npx nodemon -q -x npx ts-node src/json-type/__demos__/json-type.ts
5+
*/
6+
7+
/* tslint:disable no-console */
8+
9+
import {EncodingFormat} from '@jsonjoy.com/json-pack/lib/constants';
10+
import {TypeSystem} from '../../json-type';
11+
12+
console.clear();
13+
14+
const system = new TypeSystem();
15+
const {t} = system;
16+
17+
const type = t
18+
.Object(
19+
t.prop('id', t.str.options({ascii: true, min: 40, max: 80})).options({title: 'Every object has an ID'}),
20+
t.propOpt('name', t.str),
21+
t.prop('age', t.num.options({format: 'u8', gt: 16, lt: 100})),
22+
t.prop('verified', t.bool),
23+
t.prop('sex', t.Or(t.Const(<const>'male'), t.Const(<const>'female'), t.Const(<const>'other'), t.Const<null>(null))),
24+
)
25+
.options({
26+
title: 'My object',
27+
});
28+
29+
console.log();
30+
console.log('Print to string:');
31+
console.log();
32+
console.log(type + '');
33+
34+
console.log();
35+
console.log('Generate random value:');
36+
console.log();
37+
console.log(type.random());
38+
39+
console.log();
40+
console.log('Can output JSON Type schema:');
41+
console.log();
42+
console.log(type.getSchema());
43+
44+
console.log();
45+
console.log('Can output JSON Schema schema:');
46+
console.log();
47+
console.log(type.toJsonSchema());
48+
49+
console.log();
50+
console.log('Can export and import the schema:');
51+
const type2 = t.import(type.getSchema());
52+
console.log();
53+
console.log(type2 + '');
54+
55+
console.log();
56+
57+
console.log('Can validate the schema.');
58+
type2.validateSchema();
59+
60+
console.log('Can validate data.');
61+
type2.validate({
62+
id: '1234567890123456789012345678901234567890',
63+
name: 'John Doe',
64+
age: 18,
65+
verified: true,
66+
sex: 'male',
67+
});
68+
69+
console.log();
70+
console.log('Can serialize value to JSON:');
71+
console.log();
72+
console.log(
73+
type2.toJson({
74+
id: '1234567890123456789012345678901234567890',
75+
name: 'John Doe',
76+
age: 18,
77+
verified: true,
78+
sex: 'male',
79+
}),
80+
);
81+
82+
console.log();
83+
console.log('Can create a JSON Type schema out of a sample object:');
84+
const sample = {
85+
id: '1234567890123456789012345678901234567890',
86+
name: 'John Doe',
87+
age: 18,
88+
};
89+
const user = system.alias('User', t.from(sample));
90+
console.log();
91+
console.log(sample);
92+
console.log();
93+
console.log(user.type + '');
94+
95+
console.log();
96+
console.log('Can generate TypeScript types for a schema:');
97+
console.log();
98+
console.log(user.toTypeScriptAst());
99+
console.log();
100+
console.log(user.toTypeScript());
101+
102+
console.log();
103+
console.log('Can compile a fast JSON serializer:');
104+
console.log();
105+
console.log(user.type.compileEncoder(EncodingFormat.Json).toString());
106+
107+
console.log();
108+
console.log('Can compile a fast CBOR serializer:');
109+
console.log();
110+
console.log(user.type.compileCborEncoder({system}).toString());
111+
112+
console.log();
113+
console.log('Can compile a fast MessagePack serializer:');
114+
console.log();
115+
console.log(user.type.compileMessagePackEncoder({system}).toString());
116+
117+
console.log();
118+
console.log('Can compile a fast validator, which returns booleans as errors:');
119+
console.log();
120+
const validator = user.type.compileValidator({
121+
errors: 'boolean',
122+
skipObjectExtraFieldsCheck: true,
123+
});
124+
console.log(validator.toString());
125+
126+
console.log();
127+
console.log('Can compile a fast validator, which returns JSON strings as errors:');
128+
console.log();
129+
const validator2 = user.type.compileValidator({
130+
errors: 'string',
131+
skipObjectExtraFieldsCheck: true,
132+
});
133+
console.log(validator2.toString());
134+
135+
console.log();
136+
console.log('Can compile a fast validator, which returns objects as errors:');
137+
console.log();
138+
const validator3 = user.type.compileValidator({
139+
errors: 'object',
140+
skipObjectExtraFieldsCheck: true,
141+
});
142+
console.log(validator3.toString());

0 commit comments

Comments
 (0)