Skip to content

Commit a5d7f5b

Browse files
committed
🎉 feat: add benchmark
1 parent 1c3b53a commit a5d7f5b

File tree

10 files changed

+316
-24
lines changed

10 files changed

+316
-24
lines changed

.npmignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@
55
.cjs.swcrc
66
.es.swcrc
77
bun.lockb
8+
bun.lock
89

910
node_modules
1011
tsconfig.json
1112
pnpm-lock.yaml
1213
jest.config.js
1314
nodemon.json
15+
benchmarks
1416

1517
example
1618
tests

README.md

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,35 @@
11
# JSON Accelerator
22

3-
Accelerate JSON stringification by providing OpenAPI/TypeBox model
3+
Accelerate JSON stringification by providing OpenAPI/TypeBox model.
4+
5+
By providing model ahead of time, the library will generate a function that will serialize the object into a JSON string.
6+
7+
```
8+
$ npx tsx benchmarks/medium.ts
9+
10+
clk: ~3.02 GHz
11+
cpu: Apple M1 Max
12+
runtime: node 22.6.0 (arm64-darwin)
13+
14+
summary
15+
JSON Acclerator
16+
1.72x faster than JSON Stingify
17+
2.05x faster than Fast Json Stringify
18+
```
419

520
## Installation
621

722
```bash
23+
# Using either one of the package manager
24+
npm install json-accelerator
25+
yarn add json-accelerator
26+
pnpm add json-accelerator
827
bun add json-accelerator
928
```
1029

1130
## Usage
1231

13-
It is expected to use [https://github.com/sinclairzx81/typebox](TypeBox) to define the schema, but and OpenAPI schema should also work.
32+
It is designed to be used with [https://github.com/sinclairzx81/typebox](TypeBox) but an OpenAPI schema should also work.
1433

1534
```typescript
1635
import { Type as t } from '@sinclair/typebox'
@@ -55,8 +74,7 @@ const value = {
5574
const guard = TypeCompiler.Compile(shape)
5675
const encode = createAccelerator(shape)
5776

58-
if(guard.Check(value))
59-
encode(value)
77+
if (guard.Check(value)) encode(value)
6078
```
6179

6280
If the shape is incorrect, the output will try to corece the value into an expected model but if failed the error will be thrown.

benchmarks/medium.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { t } from 'elysia'
2+
import { benchmark } from './utils'
3+
4+
benchmark(
5+
t.Object({
6+
id: t.Number(),
7+
name: t.Literal('SaltyAom'),
8+
bio: t.String(),
9+
user: t.Object({
10+
name: t.String(),
11+
password: t.String()
12+
}),
13+
playing: t.Optional(t.String()),
14+
games: t.Array(
15+
t.Object({
16+
name: t.String(),
17+
hoursPlay: t.Number({ default: 0 }),
18+
tags: t.Array(t.String())
19+
})
20+
),
21+
metadata: t.Intersect([
22+
t.Object({
23+
alias: t.String()
24+
}),
25+
t.Object({
26+
country: t.Nullable(t.String())
27+
})
28+
]),
29+
social: t.Optional(
30+
t.Object({
31+
facebook: t.Optional(t.String()),
32+
twitter: t.Optional(t.String()),
33+
youtube: t.Optional(t.String())
34+
})
35+
)
36+
}),
37+
{
38+
id: 1,
39+
name: 'SaltyAom',
40+
bio: 'I like train',
41+
user: {
42+
name: 'SaltyAom',
43+
password: '123456'
44+
},
45+
games: [
46+
{
47+
name: 'MiSide',
48+
hoursPlay: 17,
49+
tags: ['Psychological Horror', 'Cute', 'Dating Sim']
50+
},
51+
{
52+
name: 'Strinova',
53+
hoursPlay: 365,
54+
tags: ['Free to Play', 'Anime', 'Third-Person Shooter']
55+
},
56+
{
57+
name: "Tom Clancy's Rainbow Six Siege",
58+
hoursPlay: 287,
59+
tags: ['FPS', 'Multiplayer', 'Tactical']
60+
}
61+
],
62+
metadata: {
63+
alias: 'SaltyAom',
64+
country: 'Thailand'
65+
},
66+
social: {
67+
twitter: 'SaltyAom'
68+
}
69+
}
70+
)

benchmarks/multiple-queries.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { t } from 'elysia'
2+
import { benchmark } from './utils'
3+
4+
benchmark(
5+
t.Array(
6+
t.Object({
7+
id: t.Number(),
8+
randomNumber: t.Number()
9+
})
10+
),
11+
[
12+
{ id: 4174, randomNumber: 331 },
13+
{ id: 51, randomNumber: 6544 },
14+
{ id: 4462, randomNumber: 952 },
15+
{ id: 2221, randomNumber: 532 },
16+
{ id: 9276, randomNumber: 3097 },
17+
{ id: 3056, randomNumber: 7293 },
18+
{ id: 6964, randomNumber: 620 },
19+
{ id: 675, randomNumber: 6601 },
20+
{ id: 8414, randomNumber: 6569 },
21+
{ id: 2753, randomNumber: 4065 }
22+
]
23+
)

benchmarks/small.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { t } from 'elysia'
2+
import { benchmark } from './utils'
3+
4+
benchmark(
5+
t.Object({
6+
id: t.Number(),
7+
name: t.Literal('SaltyAom'),
8+
bio: t.String(),
9+
metadata: t.Object({
10+
alias: t.String(),
11+
country: t.String()
12+
})
13+
}),
14+
{
15+
id: 1,
16+
name: 'SaltyAom',
17+
bio: 'I like train',
18+
metadata: {
19+
alias: 'SaltyAom',
20+
country: 'Thailand'
21+
}
22+
}
23+
)

benchmarks/utils.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { bench, run, barplot, summary } from 'mitata'
2+
3+
import { createAccelerator } from '../src'
4+
import fastJson from 'fast-json-stringify'
5+
import type { TAnySchema } from '@sinclair/typebox'
6+
7+
export const benchmark = <T extends TAnySchema>(
8+
model: T,
9+
value: T['static']
10+
) => {
11+
const fastJsonStringify = fastJson(model)
12+
const encode = createAccelerator(model)
13+
14+
if (encode(value) !== JSON.stringify(value))
15+
throw new Error('Invalid result')
16+
17+
barplot(() => {
18+
summary(() => {
19+
bench('JSON Stingify', () => {
20+
return JSON.stringify(value)
21+
})
22+
23+
bench('Fast Json Stringify', () => {
24+
return fastJsonStringify(value)
25+
})
26+
27+
bench('JSON Accelerator', () => {
28+
return encode(value)
29+
})
30+
})
31+
})
32+
33+
run()
34+
}

bun.lock

Lines changed: 85 additions & 0 deletions
Large diffs are not rendered by default.

example/index.ts

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,31 @@ import { t } from 'elysia'
22
import { createAccelerator } from '../src/index'
33

44
const shape = t.Object({
5-
a: t.Array(t.Number())
6-
// a: t.Literal('a'),
7-
// b: t.Number(),
8-
// c: t.Object({
9-
// a: t.String()
10-
// }),
11-
// d: t.Array(
12-
// t.Object({
13-
// a: t.Array(t.String())
14-
// })
15-
// ),
16-
// e: t.Intersect([
17-
// t.Object({
18-
// a: t.String()
19-
// }),
20-
// t.Object({
21-
// b: t.Optional(t.Number())
22-
// })
23-
// ])
5+
a: t.Literal('a'),
6+
b: t.Number(),
7+
c: t.Object({
8+
a: t.String()
9+
}),
10+
d: t.Array(
11+
t.Object({
12+
a: t.Array(t.String())
13+
})
14+
),
15+
e: t.Intersect([
16+
t.Object({
17+
a: t.String()
18+
}),
19+
t.Object({
20+
b: t.Optional(t.Number())
21+
})
22+
])
2423
})
2524

2625
const stringify = createAccelerator(shape)
2726

2827
console.log(
2928
stringify({
30-
a: [1, 2],
29+
a: 'a',
3130
b: 1,
3231
c: { a: 'a' },
3332
d: [{ a: ['a', 'b'] }, { a: ['a', 'a'] }],

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@
2222
"@types/bun": "1.2.2",
2323
"elysia": "^1.2.11",
2424
"eslint": "9.6.0",
25+
"fast-json-stringify": "^6.0.1",
26+
"mitata": "^1.0.33",
2527
"tsup": "^8.1.0",
28+
"tsx": "^4.19.2",
2629
"typescript": "^5.5.3"
2730
},
2831
"main": "./dist/cjs/index.js",

test/index.test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,22 @@ describe('Core', () => {
4040
isEqual(shape, value)
4141
})
4242

43+
it('handle null', () => {
44+
const shape = t.Null()
45+
46+
const value = null satisfies typeof shape.static
47+
48+
isEqual(shape, value)
49+
})
50+
51+
it('handle undefined', () => {
52+
const shape = t.Undefined()
53+
54+
const value = undefined satisfies typeof shape.static
55+
56+
expect(createAccelerator(shape)(value)).toBe('')
57+
})
58+
4359
it('handle object', () => {
4460
const shape = t.Object({
4561
name: t.String(),
@@ -405,4 +421,23 @@ describe('Core', () => {
405421

406422
isEqual(shape, value)
407423
})
424+
425+
it('handle additionalProperties', () => {
426+
const shape = t.Object(
427+
{
428+
name: t.String(),
429+
playing: t.Optional(t.String())
430+
},
431+
{
432+
additionalProperties: true
433+
}
434+
)
435+
436+
const value = {
437+
name: 'saltyaom',
438+
playing: 'Strinova'
439+
} satisfies typeof shape.static
440+
441+
isEqual(shape, value)
442+
})
408443
})

0 commit comments

Comments
 (0)