Skip to content

Commit 47d4795

Browse files
committed
🎉 feat: record
1 parent 65fdf8d commit 47d4795

File tree

8 files changed

+232
-9
lines changed

8 files changed

+232
-9
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
# 0.1.1 - 4 Mar 2025
2+
Feature:
3+
- support Record
4+
5+
Improvement:
6+
- add Tuple test case
7+
18
# 0.1.0 - 5 Feb 2025
29
Feature:
310
- replace `arrayItems.join('",\"')` in favour of inline `joinStringArray` to improve performance

benchmarks/small2.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { t } from 'elysia'
2+
import { benchmark } from './utils'
3+
4+
benchmark(
5+
t.Array(
6+
t.Object({
7+
name: t.String(),
8+
pwd: t.String(),
9+
id: t.Array(t.Number())
10+
})
11+
),
12+
[
13+
{
14+
name: 'SaltyAom',
15+
pwd: 'password',
16+
id: [1, 2, 3]
17+
},
18+
{
19+
name: 'JohnDoe',
20+
pwd: 'password',
21+
id: [4, 5, 6]
22+
},
23+
{
24+
name: 'JaneDoe',
25+
pwd: 'password',
26+
id: [7, 8, 9]
27+
}
28+
]
29+
)

benchmarks/utils.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { bench, run, barplot, summary, compact } from 'mitata'
22

33
import { createAccelerator } from '../src'
4+
import { TypeCompiler } from '@sinclair/typebox/compiler'
45
import fastJson from 'fast-json-stringify'
6+
57
import type { TAnySchema } from '@sinclair/typebox'
68

79
export const benchmark = <T extends TAnySchema>(
@@ -37,6 +39,14 @@ export const benchmark = <T extends TAnySchema>(
3739
bench('JSON Accelerator', () => {
3840
return encode(value)
3941
})
42+
43+
const validator = TypeCompiler.Compile(model)
44+
45+
bench('JSON Accelerator w/ validation', () => {
46+
validator.Check(value)
47+
48+
return encode(value)
49+
})
4050
})
4151
})
4252
})

example/index.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
import { t } from 'elysia'
22
import { createAccelerator } from '../src/index'
33

4-
const shape = t.Object({
5-
name: t.String(),
6-
playing: t.Nullable(t.String())
7-
})
4+
const shape = t.Record(
5+
t.String(),
6+
t.Object({
7+
a: t.String(),
8+
b: t.String()
9+
})
10+
)
811

912
const value = {
10-
name: 'saltyaom',
11-
playing: null
13+
a: { a: 'a', b: 'a' },
14+
c: { a: 'a', b: 'b' }
1215
} satisfies typeof shape.static
1316

1417
const stringify = createAccelerator(shape)

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "json-accelerator",
3-
"version": "0.1.0",
3+
"version": "0.1.1",
44
"description": "Speed up JSON stringification by providing OpenAPI/TypeBox model",
55
"license": "MIT",
66
"scripts": {

src/index.ts

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
import type { TAnySchema } from '@sinclair/typebox'
1+
import type { TAnySchema, TRecord } from '@sinclair/typebox'
22

33
const Kind = Symbol.for('TypeBox.Kind')
44
const OptionalKind = Symbol.for('TypeBox.Optional')
55

66
const isSpecialProperty = (name: string) => /(\ |-|\t|\n)/.test(name)
77

8-
const joinProperty = (v1: string, v2: string) => {
8+
const joinProperty = (v1: string, v2: string | number) => {
9+
if (typeof v2 === 'number') return `${v1}[${v2}]`
10+
911
if (isSpecialProperty(v2)) return `${v1}["${v2}"]`
1012

1113
return `${v1}.${v2}`
@@ -188,6 +190,34 @@ const joinStringArray = (p: string) =>
188190
'})()' +
189191
'}"'
190192

193+
const handleRecord = (
194+
schema: TRecord,
195+
property: string,
196+
instruction: Instruction
197+
) => {
198+
const child =
199+
schema.patternProperties['^(.*)$'] ??
200+
schema.patternProperties[Object.keys(schema.patternProperties)[0]]
201+
202+
if (!child) return property
203+
204+
const i = instruction.array
205+
instruction.array++
206+
207+
return (
208+
`\${(()=>{` +
209+
`const ar${i}s=Object.keys(${property});` +
210+
`let ar${i}v='{';` +
211+
`for(let i=0;i<ar${i}s.length;i++){` +
212+
`const ar${i}p=${property}[ar${i}s[i]];` +
213+
`if(i!==0)ar${i}v+=',';` +
214+
`ar${i}v+=\`"\${ar${i}s[i]}":${accelerate(child, `ar${i}p`, instruction)}\`` +
215+
`}` +
216+
`return ar${i}v + '}'` +
217+
`})()}`
218+
)
219+
}
220+
191221
const accelerate = (
192222
schema: TAnySchema,
193223
property: string,
@@ -276,6 +306,21 @@ const accelerate = (
276306
let hasOptional = false
277307
let op = `op${instruction.optional}`
278308

309+
if (schema[Kind as any] === 'Record') {
310+
v = handleRecord(schema as TRecord, property, instruction)
311+
312+
break
313+
}
314+
315+
if (
316+
!Object.keys(schema.properties).length &&
317+
schema.patternProperties
318+
) {
319+
v = `$\{JSON.stringify(${property})}`
320+
321+
break
322+
}
323+
279324
for (const key in schema.properties)
280325
if (OptionalKind in schema.properties[key]) {
281326
instruction.optional++

test/record.test.ts

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { t } from 'elysia'
2+
import { type TAnySchema } from '@sinclair/typebox'
3+
import { createAccelerator } from '../src'
4+
5+
import { describe, expect, it } from 'bun:test'
6+
7+
const isEqual = (shape: TAnySchema, value: unknown, expected = value) =>
8+
expect(JSON.parse(createAccelerator(shape)(value))).toEqual(expected)
9+
10+
describe('Record', () => {
11+
it('handle record', () => {
12+
const shape = t.Record(t.String(), t.String())
13+
14+
const value = {
15+
name: 'saltyaom',
16+
alias: 'saltyaom'
17+
} satisfies typeof shape.static
18+
19+
isEqual(shape, value)
20+
})
21+
22+
it('handle record object', () => {
23+
const shape = t.Record(
24+
t.String(),
25+
t.Object({
26+
name: t.String(),
27+
age: t.Number()
28+
})
29+
)
30+
31+
const value = {
32+
saltyaom: {
33+
name: 'saltyaom',
34+
age: 23
35+
},
36+
chiffon: {
37+
name: 'chiffon',
38+
age: 24
39+
}
40+
} satisfies typeof shape.static
41+
42+
isEqual(shape, value)
43+
})
44+
45+
it('handle nested record', () => {
46+
const shape = t.Record(t.String(), t.Record(t.String(), t.Number()))
47+
48+
const value = {
49+
saltyaom: {
50+
id: 1,
51+
age: 23
52+
},
53+
chiffon: {
54+
id: 2,
55+
age: 24
56+
}
57+
} satisfies typeof shape.static
58+
59+
isEqual(shape, value)
60+
})
61+
62+
it('handle unknown record', () => {
63+
const shape = t.Object(
64+
{},
65+
{
66+
patternProperties: {
67+
'^[a-z]+$': t.String()
68+
}
69+
}
70+
)
71+
72+
const value = {
73+
name: 'saltyaom',
74+
alias: 'saltyaom',
75+
unknown: {
76+
a: 1,
77+
b: ['a', { hello: 'world' }]
78+
}
79+
} satisfies typeof shape.static
80+
81+
isEqual(shape, value)
82+
})
83+
})

test/tuple.test.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { t } from 'elysia'
2+
import { type TAnySchema } from '@sinclair/typebox'
3+
import { createAccelerator } from '../src'
4+
5+
import { describe, expect, it } from 'bun:test'
6+
7+
const isEqual = (shape: TAnySchema, value: unknown, expected = value) =>
8+
expect(JSON.parse(createAccelerator(shape)(value))).toEqual(expected)
9+
10+
describe('Tuple', () => {
11+
it('handle tuple', () => {
12+
const shape = t.Tuple([t.String(), t.Number()])
13+
14+
const value = ['saltyaom', 123] satisfies typeof shape.static
15+
16+
isEqual(shape, value)
17+
})
18+
19+
it('handle tuple object', () => {
20+
const shape = t.Tuple([
21+
t.String(),
22+
t.Object({
23+
name: t.String(),
24+
age: t.Number()
25+
})
26+
])
27+
28+
const value = [
29+
'a',
30+
{
31+
name: 'saltyaom',
32+
age: 123
33+
}
34+
] satisfies typeof shape.static
35+
36+
isEqual(shape, value)
37+
})
38+
39+
it('handle nested tuple', () => {
40+
const shape = t.Tuple([t.String(), t.Tuple([t.String(), t.Number()])])
41+
42+
const value = ['a', ['b', 123]] satisfies typeof shape.static
43+
44+
isEqual(shape, value)
45+
})
46+
})

0 commit comments

Comments
 (0)