Skip to content

Commit

Permalink
Improve performance for const schemas (#511)
Browse files Browse the repository at this point in the history
* Improved performance for const schema

* Improve const object schema performance

* Improve string and object performance

* Restored example file

* Changed findIndex with indexOf

* Removed useless inference when schema is const

* Review requests
- Removed switchTypeSchema
- Use of variable isRequired where defined
- Rebase to PR #510
- Remove use of default value for const

* Rebase to #510 (use hasOwnProperty from Object.prototype)

* Use isRequired when available

* Restored FJS format style

* Restore FJS format style

* Added support for nullable / type: [..., 'null']
  • Loading branch information
DanieleFedeli committed Aug 29, 2022
1 parent 03660fd commit ddb1c48
Show file tree
Hide file tree
Showing 3 changed files with 302 additions and 16 deletions.
54 changes: 54 additions & 0 deletions benchmark/bench.js
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,60 @@ const benchmarks = [
n5: 42,
b5: true
}
},
{
name: 'object with const string property',
schema: {
type: 'object',
properties: {
a: { const: 'const string' }
}
},
input: { a: 'const string' }
},
{
name: 'object with const number property',
schema: {
type: 'object',
properties: {
a: { const: 1 }
}
},
input: { a: 1 }
},
{
name: 'object with const bool property',
schema: {
type: 'object',
properties: {
a: { const: true }
}
},
input: { a: true }
},
{
name: 'object with const object property',
schema: {
type: 'object',
properties: {
foo: { const: { bar: 'baz' } }
}
},
input: {
foo: { bar: 'baz' }
}
},
{
name: 'object with const null property',
schema: {
type: 'object',
properties: {
foo: { const: null }
}
},
input: {
foo: null
}
}
]

Expand Down
21 changes: 13 additions & 8 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,7 @@ function buildCode (location) {
code += buildValue(propertyLocation, `obj[${JSON.stringify(key)}]`)

const defaultValue = schema.properties[key].default

if (defaultValue !== undefined) {
code += `
} else {
Expand Down Expand Up @@ -796,7 +797,7 @@ function buildValue (location, input) {
}

let type = schema.type
const nullable = schema.nullable === true
const nullable = schema.nullable === true || (Array.isArray(type) && type.includes('null'))

let code = ''
let funcName
Expand All @@ -805,6 +806,17 @@ function buildValue (location, input) {
type = 'string'
}

if ('const' in schema) {
if (nullable) {
code += `
json += ${input} === null ? 'null' : '${JSON.stringify(schema.const)}'
`
return code
}
code += `json += '${JSON.stringify(schema.const)}'`
return code
}

switch (type) {
case 'null':
code += 'json += serializer.asNull()'
Expand Down Expand Up @@ -865,13 +877,6 @@ function buildValue (location, input) {
code += `
json += JSON.stringify(${input})
`
} else if ('const' in schema) {
code += `
if(ajv.validate(${JSON.stringify(schema)}, ${input}))
json += '${JSON.stringify(schema.const)}'
else
throw new Error(\`Item $\{JSON.stringify(${input})} does not match schema definition.\`)
`
} else if (schema.type === undefined) {
code += `
json += JSON.stringify(${input})
Expand Down
243 changes: 235 additions & 8 deletions test/const.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,184 @@ test('schema with const string', (t) => {
t.ok(validate(JSON.parse(output)), 'valid schema')
})

test('schema with const string and different input', (t) => {
t.plan(2)

const schema = {
type: 'object',
properties: {
foo: { const: 'bar' }
}
}

const validate = validator(schema)
const stringify = build(schema)
const output = stringify({
foo: 'baz'
})

t.equal(output, '{"foo":"bar"}')
t.ok(validate(JSON.parse(output)), 'valid schema')
})

test('schema with const string and different type input', (t) => {
t.plan(2)

const schema = {
type: 'object',
properties: {
foo: { const: 'bar' }
}
}

const validate = validator(schema)
const stringify = build(schema)
const output = stringify({
foo: 1
})

t.equal(output, '{"foo":"bar"}')
t.ok(validate(JSON.parse(output)), 'valid schema')
})

test('schema with const string and no input', (t) => {
t.plan(2)

const schema = {
type: 'object',
properties: {
foo: { const: 'bar' }
}
}

const validate = validator(schema)
const stringify = build(schema)
const output = stringify({})

t.equal(output, '{}')
t.ok(validate(JSON.parse(output)), 'valid schema')
})

test('schema with const number', (t) => {
t.plan(2)

const schema = {
type: 'object',
properties: {
foo: { const: 1 }
}
}

const validate = validator(schema)
const stringify = build(schema)
const output = stringify({
foo: 1
})

t.equal(output, '{"foo":1}')
t.ok(validate(JSON.parse(output)), 'valid schema')
})

test('schema with const number and different input', (t) => {
t.plan(2)

const schema = {
type: 'object',
properties: {
foo: { const: 1 }
}
}

const validate = validator(schema)
const stringify = build(schema)
const output = stringify({
foo: 2
})

t.equal(output, '{"foo":1}')
t.ok(validate(JSON.parse(output)), 'valid schema')
})

test('schema with const bool', (t) => {
t.plan(2)

const schema = {
type: 'object',
properties: {
foo: { const: true }
}
}

const validate = validator(schema)
const stringify = build(schema)
const output = stringify({
foo: true
})

t.equal(output, '{"foo":true}')
t.ok(validate(JSON.parse(output)), 'valid schema')
})

test('schema with const number', (t) => {
t.plan(2)

const schema = {
type: 'object',
properties: {
foo: { const: 1 }
}
}

const validate = validator(schema)
const stringify = build(schema)
const output = stringify({
foo: 1
})

t.equal(output, '{"foo":1}')
t.ok(validate(JSON.parse(output)), 'valid schema')
})

test('schema with const null', (t) => {
t.plan(2)

const schema = {
type: 'object',
properties: {
foo: { const: null }
}
}

const validate = validator(schema)
const stringify = build(schema)
const output = stringify({
foo: null
})

t.equal(output, '{"foo":null}')
t.ok(validate(JSON.parse(output)), 'valid schema')
})

test('schema with const array', (t) => {
t.plan(2)

const schema = {
type: 'object',
properties: {
foo: { const: [1, 2, 3] }
}
}

const validate = validator(schema)
const stringify = build(schema)
const output = stringify({
foo: [1, 2, 3]
})

t.equal(output, '{"foo":[1,2,3]}')
t.ok(validate(JSON.parse(output)), 'valid schema')
})

test('schema with const object', (t) => {
t.plan(2)

Expand All @@ -44,6 +222,56 @@ test('schema with const object', (t) => {
t.ok(validate(JSON.parse(output)), 'valid schema')
})

test('schema with const and null as type', (t) => {
t.plan(4)

const schema = {
type: 'object',
properties: {
foo: { type: ['string', 'null'], const: 'baz' }
}
}

const validate = validator(schema)
const stringify = build(schema)
const output = stringify({
foo: null
})

t.equal(output, '{"foo":null}')
t.ok(validate(JSON.parse(output)), 'valid schema')

const output2 = stringify({ foo: 'baz' })
t.equal(output2, '{"foo":"baz"}')
t.ok(validate(JSON.parse(output2)), 'valid schema')
})

test('schema with const as nullable', (t) => {
t.plan(4)

const schema = {
type: 'object',
properties: {
foo: { nullable: true, const: 'baz' }
}
}

const validate = validator(schema)
const stringify = build(schema)
const output = stringify({
foo: null
})

t.equal(output, '{"foo":null}')
t.ok(validate(JSON.parse(output)), 'valid schema')

const output2 = stringify({
foo: 'baz'
})
t.equal(output2, '{"foo":"baz"}')
t.ok(validate(JSON.parse(output2)), 'valid schema')
})

test('schema with const and invalid object', (t) => {
t.plan(2)

Expand All @@ -55,13 +283,12 @@ test('schema with const and invalid object', (t) => {
required: ['foo']
}

const validate = validator(schema)
const stringify = build(schema)
try {
stringify({
foo: { foo: 'baz' }
})
} catch (err) {
t.match(err.message, /^Item .* does not match schema definition/, 'Given object has invalid const value')
t.ok(err)
}
const result = stringify({
foo: { foo: 'baz' }
})

t.equal(result, '{"foo":{"foo":"bar"}}')
t.ok(validate(JSON.parse(result)), 'valid schema')
})

0 comments on commit ddb1c48

Please sign in to comment.