Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 10100d7

Browse files
authoredAug 25, 2021
Ajv8 upgrade (fastify#353)
* upgrade codebase to ajv8 * fix ajv warnings * update docs * fix format within debugMode * update docs spec * fix regression
1 parent 6a805de commit 10100d7

10 files changed

+1257
-23
lines changed
 

‎README.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,14 @@
77
[![NPM downloads](https://img.shields.io/npm/dm/fast-json-stringify.svg?style=flat)](https://www.npmjs.com/package/fast-json-stringify)
88

99

10-
__fast-json-stringify__ is significantly faster than `JSON.stringify()` for small payloads. Its performance advantage shrinks as your payload grows. It pairs well with [__flatstr__](https://www.npmjs.com/package/flatstr), which triggers a V8 optimization that improves performance when eventually converting the string to a `Buffer`.
10+
__fast-json-stringify__ is significantly faster than `JSON.stringify()` for small payloads.
11+
Its performance advantage shrinks as your payload grows.
12+
It pairs well with [__flatstr__](https://www.npmjs.com/package/flatstr), which triggers a V8 optimization that improves performance when eventually converting the string to a `Buffer`.
13+
14+
15+
### How it works
16+
17+
fast-json-stringify requires a [JSON Schema Draft 7](https://json-schema.org/specification-links.html#draft-7) input to generate a fast `stringify` function.
1118

1219
##### Benchmarks
1320

@@ -99,7 +106,7 @@ const stringify = fastJson(mySchema, {
99106
```
100107

101108
- `schema`: external schemas references by $ref property. [More details](#ref)
102-
- `ajv`: ajv instance's settings for those properties that require `ajv`. [More details](#anyof)
109+
- `ajv`: [ajv v8 instance's settings](https://ajv.js.org/options.html) for those properties that require `ajv`. [More details](#anyof)
103110
- `rounding`: setup how the `integer` types will be rounded when not integers. [More details](#integer)
104111

105112

@@ -108,7 +115,7 @@ const stringify = fastJson(mySchema, {
108115
<a name="fastJsonStringify"></a>
109116
### fastJsonStringify(schema)
110117

111-
Build a `stringify()` function based on [jsonschema](https://json-schema.org/).
118+
Build a `stringify()` function based on [jsonschema draft 7 spec](https://json-schema.org/specification-links.html#draft-7).
112119

113120
Supported types:
114121

‎build-schema-validator.js

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,26 @@
11
'use strict'
22

3-
const Ajv = require('ajv').default
4-
const schema = require('ajv/dist/refs/json-schema-draft-07.json')
5-
const standalone = require('ajv/dist/standalone').default
3+
const Ajv = require('ajv')
4+
const standaloneCode = require('ajv/dist/standalone').default
5+
const ajvFormats = require('ajv-formats')
6+
const fs = require('fs')
7+
const path = require('path')
68

7-
const { basename, join } = require('path')
8-
const { writeFileSync } = require('fs')
9+
const ajv = new Ajv({
10+
addUsedSchema: false,
11+
allowUnionTypes: true,
12+
code: {
13+
source: true,
14+
lines: true,
15+
optimize: 3
16+
}
17+
})
18+
ajvFormats(ajv)
919

10-
// *************************
20+
const schema = require('ajv/lib/refs/json-schema-draft-07.json')
21+
const validate = ajv.compile(schema)
22+
const validationCode = standaloneCode(ajv, validate)
1123

12-
const ajv = new Ajv({ code: { source: true } })
13-
const validator = ajv.compile(schema)
14-
const moduleCode = `/* CODE GENERATED BY '${basename(__filename)}' DO NOT EDIT! */\n${standalone(ajv, validator)}`
24+
const moduleCode = `/* CODE GENERATED BY '${path.basename(__filename)}' DO NOT EDIT! */\n${validationCode}`
1525

16-
writeFileSync(join(__dirname, 'schema-validator.js'), moduleCode)
26+
fs.writeFileSync(path.join(__dirname, 'schema-validator.js'), moduleCode)

‎index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
/* eslint no-prototype-builtins: 0 */
44

5-
const Ajv = require('ajv').default
5+
const Ajv = require('ajv')
66
const ajvFormats = require('ajv-formats')
77
const merge = require('deepmerge')
88
const clone = require('rfdc')({ proto: true })
@@ -27,7 +27,7 @@ function isValidSchema (schema, name) {
2727
name = ''
2828
}
2929
const first = validate.errors[0]
30-
const err = new Error(`${name}schema is invalid: data${first.dataPath} ${first.message}`)
30+
const err = new Error(`${name}schema is invalid: data${first.instancePath} ${first.message}`)
3131
err.errors = isValidSchema.errors
3232
throw err
3333
}

‎package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@
4343
"webpack": "^5.40.0"
4444
},
4545
"dependencies": {
46-
"ajv": "^7.2.3",
47-
"ajv-formats": "^1.6.1",
46+
"ajv": "^8.6.2",
47+
"ajv-formats": "^2.1.1",
4848
"deepmerge": "^4.2.2",
4949
"rfdc": "^1.2.0",
5050
"string-similarity": "^4.0.1"

‎schema-validator.js

Lines changed: 1133 additions & 1 deletion
Large diffs are not rendered by default.

‎test/allof.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ test('object with allOf and no schema on the allOf', (t) => {
108108
build(schema)
109109
t.fail()
110110
} catch (e) {
111-
t.equal(e.message, 'schema is invalid: data/allOf should NOT have fewer than 1 items')
111+
t.equal(e.message, 'schema is invalid: data/allOf must NOT have fewer than 1 items')
112112
}
113113
})
114114

‎test/debug-mode.test.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,29 @@ test('to string auto-consistent with ajv', t => {
6363
const tobe = JSON.stringify({ str: 'Foo' })
6464
t.same(compiled({ str: 'Foo', void: 'me' }), tobe)
6565
})
66+
67+
test('to string auto-consistent with ajv-formats', t => {
68+
t.plan(3)
69+
const debugMode = fjs({
70+
title: 'object with multiple types field and format keyword',
71+
type: 'object',
72+
properties: {
73+
str: {
74+
anyOf: [{
75+
type: 'string',
76+
format: 'email'
77+
}, {
78+
type: 'boolean'
79+
}]
80+
}
81+
}
82+
}, { debugMode: 1 })
83+
t.type(debugMode, Array)
84+
85+
const str = debugMode.toString()
86+
87+
const compiled = fjs.restore(str)
88+
const tobe = JSON.stringify({ str: 'foo@bar.com' })
89+
t.same(compiled({ str: 'foo@bar.com' }), tobe)
90+
t.same(compiled({ str: 'foo' }), JSON.stringify({ str: null }), 'invalid format is ignored')
91+
})

‎test/if-then-else.test.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@ const schema = {
88
properties: {
99
},
1010
if: {
11+
type: 'object',
1112
properties: {
1213
kind: { type: 'string', enum: ['foobar'] }
1314
}
1415
},
1516
then: {
17+
type: 'object',
1618
properties: {
1719
kind: { type: 'string', enum: ['foobar'] },
1820
foo: { type: 'string' },
@@ -30,6 +32,7 @@ const schema = {
3032
}
3133
},
3234
else: {
35+
type: 'object',
3336
properties: {
3437
kind: { type: 'string', enum: ['greeting'] },
3538
hi: { type: 'string' },
@@ -52,17 +55,20 @@ const nestedIfSchema = {
5255
type: 'object',
5356
properties: { },
5457
if: {
58+
type: 'object',
5559
properties: {
5660
kind: { type: 'string', enum: ['foobar', 'greeting'] }
5761
}
5862
},
5963
then: {
6064
if: {
65+
type: 'object',
6166
properties: {
6267
kind: { type: 'string', enum: ['foobar'] }
6368
}
6469
},
6570
then: {
71+
type: 'object',
6672
properties: {
6773
kind: { type: 'string', enum: ['foobar'] },
6874
foo: { type: 'string' },
@@ -80,6 +86,7 @@ const nestedIfSchema = {
8086
}
8187
},
8288
else: {
89+
type: 'object',
8390
properties: {
8491
kind: { type: 'string', enum: ['greeting'] },
8592
hi: { type: 'string' },
@@ -88,6 +95,7 @@ const nestedIfSchema = {
8895
}
8996
},
9097
else: {
98+
type: 'object',
9199
properties: {
92100
kind: { type: 'string', enum: ['alphabet'] },
93101
a: { type: 'string' },
@@ -100,11 +108,13 @@ const nestedElseSchema = {
100108
type: 'object',
101109
properties: { },
102110
if: {
111+
type: 'object',
103112
properties: {
104113
kind: { type: 'string', enum: ['foobar'] }
105114
}
106115
},
107116
then: {
117+
type: 'object',
108118
properties: {
109119
kind: { type: 'string', enum: ['foobar'] },
110120
foo: { type: 'string' },
@@ -123,18 +133,21 @@ const nestedElseSchema = {
123133
},
124134
else: {
125135
if: {
136+
type: 'object',
126137
properties: {
127138
kind: { type: 'string', enum: ['greeting'] }
128139
}
129140
},
130141
then: {
142+
type: 'object',
131143
properties: {
132144
kind: { type: 'string', enum: ['greeting'] },
133145
hi: { type: 'string' },
134146
hello: { type: 'number' }
135147
}
136148
},
137149
else: {
150+
type: 'object',
138151
properties: {
139152
kind: { type: 'string', enum: ['alphabet'] },
140153
a: { type: 'string' },
@@ -154,11 +167,13 @@ const noElseSchema = {
154167
properties: {
155168
},
156169
if: {
170+
type: 'object',
157171
properties: {
158172
kind: { type: 'string', enum: ['foobar'] }
159173
}
160174
},
161175
then: {
176+
type: 'object',
162177
properties: {
163178
kind: { type: 'string', enum: ['foobar'] },
164179
foo: { type: 'string' },

‎test/nullable.test.js

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,16 +97,21 @@ const testSet = {
9797
items: {}
9898
}, null, null],
9999
nullableObject: [{ type: 'object', nullable: true }, null, null],
100-
complexObject: [complexObject, complexData, complexExpectedResult]
100+
complexObject: [complexObject, complexData, complexExpectedResult, { ajv: { allowUnionTypes: true } }]
101101
}
102102

103103
Object.keys(testSet).forEach(key => {
104104
test(`handle nullable:true in ${key} correctly`, (t) => {
105105
t.plan(1)
106106

107-
const stringifier = build(testSet[key][0])
108-
const data = testSet[key][1]
109-
const expected = testSet[key][2]
107+
const [
108+
schema,
109+
data,
110+
expected,
111+
extraOptions
112+
] = testSet[key]
113+
114+
const stringifier = build(schema, extraOptions)
110115
const result = stringifier(data)
111116
t.same(JSON.parse(result), expected)
112117
})

‎test/side-effect.test.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,3 +155,42 @@ test('must not mutate items $ref', t => {
155155
t.equal(value, '[{"name":"foo"}]')
156156
t.same(schema, clonedSchema)
157157
})
158+
159+
test('must not mutate items referred by $ref', t => {
160+
t.plan(2)
161+
162+
const firstSchema = {
163+
$id: 'example1',
164+
type: 'object',
165+
properties: {
166+
name: {
167+
type: 'string'
168+
}
169+
}
170+
}
171+
172+
const reusedSchema = {
173+
$id: 'example2',
174+
type: 'object',
175+
properties: {
176+
name: {
177+
oneOf: [
178+
{
179+
$ref: 'example1'
180+
}
181+
]
182+
}
183+
}
184+
}
185+
186+
const clonedSchema = clone(firstSchema)
187+
const stringify = build(reusedSchema, {
188+
schema: {
189+
[firstSchema.$id]: firstSchema
190+
}
191+
})
192+
193+
const value = stringify({ name: { name: 'foo' } })
194+
t.equal(value, '{"name":{"name":"foo"}}')
195+
t.same(firstSchema, clonedSchema)
196+
})

0 commit comments

Comments
 (0)
Please sign in to comment.