From ddb1c482c8dbe31bb77e184422b48320e905c2e8 Mon Sep 17 00:00:00 2001 From: Daniele Fedeli Date: Mon, 29 Aug 2022 12:23:05 +0200 Subject: [PATCH] Improve performance for const schemas (#511) * 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'] --- benchmark/bench.js | 54 ++++++++++ index.js | 21 ++-- test/const.test.js | 243 +++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 302 insertions(+), 16 deletions(-) diff --git a/benchmark/bench.js b/benchmark/bench.js index 4526f250..e655cdb9 100644 --- a/benchmark/bench.js +++ b/benchmark/bench.js @@ -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 + } } ] diff --git a/index.js b/index.js index 3fdac9a4..91a2977a 100644 --- a/index.js +++ b/index.js @@ -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 { @@ -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 @@ -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()' @@ -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}) diff --git a/test/const.test.js b/test/const.test.js index 72def12f..c0b0b0aa 100644 --- a/test/const.test.js +++ b/test/const.test.js @@ -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) @@ -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) @@ -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') })