From efdcfcbe65fd45dcea0bf173b94247733374da66 Mon Sep 17 00:00:00 2001 From: RelevantShannon Date: Wed, 15 Jul 2026 17:29:25 +1000 Subject: [PATCH 1/2] Optimize Ajv refs for external schema branches --- index.js | 49 +++++++- test/anyof.test.js | 42 +++++++ test/if-then-else.test.js | 112 ++++++++++++++++++ test/oneof.test.js | 242 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 443 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index b900ad7e..5508c0a1 100644 --- a/index.js +++ b/index.js @@ -100,6 +100,51 @@ function getSafeSchemaRef (context, location) { return schemaRef } +function getValidatorSchemaRef (context, location) { + let schemaRef = location.getSchemaRef() + const schema = location.schema + + // A schema that is only a registered $ref can validate against that target + // instead of a JSON-pointer path through the root schema. + if ( + typeof schema.$ref !== 'string' || + Object.keys(schema).length !== 1 + ) { + return schemaRef + } + + const ref = schema.$ref + const hashIndex = ref.indexOf('#') + const refSchemaId = hashIndex === -1 ? ref : ref.slice(0, hashIndex) + const refJsonPointer = hashIndex === -1 ? '' : ref.slice(hashIndex) + const isBareRef = refJsonPointer === '' || refJsonPointer === '#' + const isNamedFragmentRef = /^#[A-Za-z_][-A-Za-z0-9._]*$/.test(refJsonPointer) + const isJsonPointerRef = refJsonPointer.startsWith('#/') + + if ( + (isBareRef || isNamedFragmentRef || isJsonPointerRef) && + refSchemaId && + context.refResolver.hasSchema(refSchemaId) + ) { + const refSchema = context.refResolver.getSchema(refSchemaId) + const refSubSchema = isBareRef + ? refSchema + : context.refResolver.getSchema(refSchemaId, refJsonPointer) + const hasRelativeSchemaId = ( + typeof refSchema.$id === 'string' && + refSchema.$id.charAt(0) === '#' + ) + if ( + refSubSchema !== null && + (!hasRelativeSchemaId || refSchema.$id === refJsonPointer) + ) { + schemaRef = refSchemaId + (isBareRef ? '#' : refJsonPointer) + } + } + + return schemaRef +} + function build (schema, options) { isValidSchema(schema) @@ -1148,7 +1193,7 @@ function buildOneOf (context, location, input) { } const nestedResult = buildValue(context, mergedLocation, input) - const schemaRef = optionLocation.getSchemaRef() + const schemaRef = getValidatorSchemaRef(context, optionLocation) code += ` ${index === 0 ? 'if' : 'else if'}(validator.validate("${schemaRef}", ${input})) { @@ -1181,7 +1226,7 @@ function buildIfThenElse (context, location, input) { ) const ifLocation = location.getPropertyLocation('if') - const ifSchemaRef = ifLocation.getSchemaRef() + const ifSchemaRef = getValidatorSchemaRef(context, ifLocation) const thenLocation = location.getPropertyLocation('then') let thenMergedSchemaId = context.mergedSchemasIds.get(thenSchema) diff --git a/test/anyof.test.js b/test/anyof.test.js index b359761a..709d2d89 100644 --- a/test/anyof.test.js +++ b/test/anyof.test.js @@ -387,6 +387,48 @@ test('anyOf and $ref - multiple external $ref', (t) => { t.assert.equal(output, '{"obj":{"prop":{"prop2":"test"}}}') }) +test('anyOf with registered $ref branches validates directly against the ref target', (t) => { + t.plan(3) + + const schema = { + title: 'anyOf with registered $ref branches', + type: 'object', + properties: { + content: { + anyOf: [ + { $ref: 'Foo#' }, + { $ref: 'Bar#/definitions/Baz' } + ] + } + } + } + const externalSchema = { + Foo: { + type: 'object', + properties: { a: { type: 'string' } }, + required: ['a'], + additionalProperties: false + }, + Bar: { + definitions: { + Baz: { + type: 'object', + properties: { b: { type: 'string' } }, + required: ['b'], + additionalProperties: false + } + } + } + } + + const code = build(schema, { mode: 'standalone', schema: externalSchema }) + t.assert.ok(code.includes('validator.validate("Foo#"'), 'validates directly against the bare registered id') + t.assert.ok(code.includes('validator.validate("Bar#/definitions/Baz"'), 'validates directly against the registered sub-path ref') + + const stringify = build(schema, { schema: externalSchema }) + t.assert.equal(stringify({ content: { b: 'yo' } }), '{"content":{"b":"yo"}}') +}) + test('anyOf looks for all of the array items', (t) => { t.plan(1) diff --git a/test/if-then-else.test.js b/test/if-then-else.test.js index 804669e8..9220a516 100644 --- a/test/if-then-else.test.js +++ b/test/if-then-else.test.js @@ -538,3 +538,115 @@ test('if-then-else with allOf', (t) => { t.assert.doesNotThrow(() => JSON.parse(output)) t.assert.equal(output, '{"base":"test","foo":"value"}') }) + +test('if with a bare top-level $ref validates directly against the registered schema', (t) => { + t.plan(2) + + const schema = { + type: 'object', + if: { $ref: 'IsFoo#' }, + then: { + type: 'object', + properties: { + foo: { type: 'string' } + } + }, + else: { + type: 'object', + properties: { + bar: { type: 'string' } + } + } + } + const externalSchema = { + IsFoo: { + type: 'object', + properties: { + kind: { type: 'string', const: 'foo' } + }, + required: ['kind'] + } + } + + const code = build(schema, { mode: 'standalone', schema: externalSchema }) + t.assert.ok(code.includes('validator.validate("IsFoo#"'), 'validates directly against the bare registered id') + + const stringify = build(schema, { schema: externalSchema }) + t.assert.equal(stringify({ kind: 'foo', foo: 'value', bar: 'ignored' }), '{"foo":"value"}') +}) + +test('if with a $ref into a JSON-pointer sub-path validates directly against that sub-path', (t) => { + t.plan(2) + + const schema = { + type: 'object', + if: { $ref: 'Rules#/definitions/IsFoo' }, + then: { + type: 'object', + properties: { + foo: { type: 'string' } + } + }, + else: { + type: 'object', + properties: { + bar: { type: 'string' } + } + } + } + const externalSchema = { + Rules: { + definitions: { + IsFoo: { + type: 'object', + properties: { + kind: { type: 'string', const: 'foo' } + }, + required: ['kind'] + } + } + } + } + + const code = build(schema, { mode: 'standalone', schema: externalSchema }) + t.assert.ok(code.includes('validator.validate("Rules#/definitions/IsFoo"'), 'validates directly against the registered sub-path ref') + + const stringify = build(schema, { schema: externalSchema }) + t.assert.equal(stringify({ kind: 'foo', foo: 'value', bar: 'ignored' }), '{"foo":"value"}') +}) + +test('if with a $ref that has sibling keywords is left unchanged', (t) => { + t.plan(2) + + const schema = { + type: 'object', + if: { $ref: 'IsFoo#', required: ['extra'] }, + then: { + type: 'object', + properties: { + foo: { type: 'string' } + } + }, + else: { + type: 'object', + properties: { + bar: { type: 'string' } + } + } + } + const externalSchema = { + IsFoo: { + type: 'object', + properties: { + kind: { type: 'string', const: 'foo' } + }, + required: ['kind'] + } + } + + const code = build(schema, { mode: 'standalone', schema: externalSchema }) + t.assert.ok(!code.includes('validator.validate("IsFoo#"'), 'ref with sibling keywords is not redirected') + + const stringify = build(schema, { schema: externalSchema }) + t.assert.equal(stringify({ kind: 'foo', foo: 'ignored', bar: 'fallback' }), '{"bar":"fallback"}') +}) diff --git a/test/oneof.test.js b/test/oneof.test.js index 01c2f3bf..18064e63 100644 --- a/test/oneof.test.js +++ b/test/oneof.test.js @@ -348,6 +348,248 @@ test('oneOf and $ref - multiple external $ref', (t) => { t.assert.equal(output, '{"obj":{"prop":{"prop2":"test"}}}') }) +test('oneOf with a bare top-level $ref branch validates directly against the registered schema, not a root-relative pointer', (t) => { + t.plan(3) + + const schema = { + title: 'oneOf with bare $ref branches', + type: 'object', + properties: { + content: { + oneOf: [ + { $ref: 'Foo#' }, + { $ref: 'Bar#' } + ] + } + } + } + const externalSchema = { + Foo: { + type: 'object', + properties: { a: { type: 'string' } }, + required: ['a'], + additionalProperties: false + }, + Bar: { + type: 'object', + properties: { b: { type: 'string' } }, + required: ['b'], + additionalProperties: false + } + } + + const code = build(schema, { mode: 'standalone', schema: externalSchema }) + t.assert.ok(code.includes('validator.validate("Foo#"'), 'validates directly against the bare registered id') + t.assert.ok(code.includes('validator.validate("Bar#"'), 'validates directly against the bare registered id') + + const stringify = build(schema, { schema: externalSchema }) + t.assert.equal(stringify({ content: { b: 'yo' } }), '{"content":{"b":"yo"}}') +}) + +test('oneOf with a bare top-level $ref branch to a $id schema validates directly against the registered schema', (t) => { + t.plan(2) + + const schema = { + title: 'oneOf with bare $id $ref branch', + type: 'object', + properties: { + content: { + oneOf: [ + { $ref: 'urn:fjs:test:Foo#' }, + { type: 'null' } + ] + } + } + } + const externalSchema = { + Foo: { + $id: 'urn:fjs:test:Foo', + type: 'object', + properties: { a: { type: 'string' } }, + required: ['a'], + additionalProperties: false + } + } + + const code = build(schema, { mode: 'standalone', schema: externalSchema }) + t.assert.ok(code.includes('validator.validate("urn:fjs:test:Foo#"'), 'validates directly against the bare registered $id') + + const stringify = build(schema, { schema: externalSchema }) + t.assert.equal(stringify({ content: { a: 'yo' } }), '{"content":{"a":"yo"}}') +}) + +test('oneOf with a bare top-level $ref branch to a relative $id schema is left unchanged', (t) => { + t.plan(1) + + const schema = { + title: 'oneOf with bare $ref branch to relative $id', + type: 'object', + properties: { + content: { + oneOf: [ + { $ref: 'Foo#' }, + { type: 'null' } + ] + } + } + } + const externalSchema = { + Foo: { + $id: '#foo', + type: 'object', + properties: { a: { type: 'string' } }, + required: ['a'] + } + } + + const code = build(schema, { mode: 'standalone', schema: externalSchema }) + t.assert.ok(!code.includes('validator.validate("Foo#"'), 'relative $id schema is not redirected to the bare parent id') +}) + +test('oneOf with a $ref branch to a relative root $id validates directly against the registered schema', (t) => { + t.plan(2) + + const schema = { + title: 'oneOf with $ref branch to relative root $id', + type: 'object', + properties: { + content: { + oneOf: [ + { $ref: 'Foo#foo' }, + { type: 'null' } + ] + } + } + } + const externalSchema = { + Foo: { + $id: '#foo', + type: 'object', + properties: { a: { type: 'string' } }, + required: ['a'] + } + } + + const code = build(schema, { mode: 'standalone', schema: externalSchema }) + t.assert.ok(code.includes('validator.validate("Foo#foo"'), 'validates directly against the relative root $id') + + const stringify = build(schema, { schema: externalSchema }) + t.assert.equal(stringify({ content: { a: 'yo' } }), '{"content":{"a":"yo"}}') +}) + +test('oneOf with a named-fragment $ref branch validates directly against the registered schema anchor', (t) => { + t.plan(2) + + const schema = { + title: 'oneOf with named-fragment $ref branch', + type: 'object', + properties: { + content: { + oneOf: [ + { $ref: 'Foo#tag' }, + { type: 'null' } + ] + } + } + } + const externalSchema = { + Foo: { + $id: 'Foo', + $defs: { + tag: { $id: '#tag', type: 'string' } + } + } + } + + const code = build(schema, { mode: 'standalone', schema: externalSchema }) + t.assert.ok(code.includes('validator.validate("Foo#tag"'), 'validates directly against the registered schema anchor') + + const stringify = build(schema, { schema: externalSchema }) + t.assert.equal(stringify({ content: 'yo' }), '{"content":"yo"}') +}) + +test('oneOf with a $ref into a JSON-pointer sub-path validates directly against that sub-path', (t) => { + t.plan(2) + + const schema = { + title: 'oneOf with a sub-path $ref branch', + type: 'object', + properties: { + content: { + oneOf: [ + { $ref: 'Foo#/definitions/Bar' }, + { type: 'null' } + ] + } + } + } + const externalSchema = { + Foo: { + definitions: { + Bar: { type: 'object', properties: { b: { type: 'string' } } } + } + } + } + + const code = build(schema, { mode: 'standalone', schema: externalSchema }) + t.assert.ok(code.includes('validator.validate("Foo#/definitions/Bar"'), 'validates directly against the sub-path ref') + + const stringify = build(schema, { schema: externalSchema }) + t.assert.equal(stringify({ content: { b: 'yo' } }), '{"content":{"b":"yo"}}') +}) + +test('oneOf with a $ref branch that has sibling keywords is left unchanged', (t) => { + t.plan(2) + + const schema = { + title: 'oneOf with a $ref branch with sibling keywords', + type: 'object', + properties: { + content: { + oneOf: [ + { $ref: 'Foo#', required: ['c'] }, + { type: 'null' } + ] + } + } + } + const externalSchema = { + Foo: { + type: 'object', + properties: { a: { type: 'string' } }, + required: ['a'] + } + } + + const code = build(schema, { mode: 'standalone', schema: externalSchema }) + t.assert.ok(!code.includes('validator.validate("Foo#"'), 'ref with sibling keywords is not redirected') + + const stringify = build(schema, { schema: externalSchema }) + t.assert.throws(() => stringify({ content: { a: 'yo' } })) +}) + +test('oneOf with an inline (non-$ref) branch is left unchanged', (t) => { + t.plan(1) + + const schema = { + title: 'oneOf with an inline branch, no $ref', + type: 'object', + properties: { + content: { + oneOf: [ + { type: 'string' }, + { type: 'object', properties: { a: { type: 'string' } }, required: ['a'] } + ] + } + } + } + + // Should compile and serialize exactly as before -- this is a pure + // regression guard, not exercising the new code path at all. + const stringify = build(schema) + t.assert.equal(stringify({ content: 'hello' }), '{"content":"hello"}') +}) + test('oneOf with enum with more than 100 entries', (t) => { t.plan(1) From 0886d697237c4a273f84254468810760ba8b7f77 Mon Sep 17 00:00:00 2001 From: RelevantShannon Date: Tue, 21 Jul 2026 15:48:04 +1000 Subject: [PATCH 2/2] Add benchmark for external ref branches --- benchmark/bench-thread.js | 8 +++- benchmark/bench.js | 86 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 2 deletions(-) diff --git a/benchmark/bench-thread.js b/benchmark/bench-thread.js index 466440a1..91f3cab8 100644 --- a/benchmark/bench-thread.js +++ b/benchmark/bench-thread.js @@ -15,10 +15,14 @@ const bench = new Bench({ }) const FJS = require('..') -const stringify = FJS(benchmark.schema) +const stringify = benchmark.compile ? null : FJS(benchmark.schema, benchmark.options) bench.add(benchmark.name, () => { - stringify(benchmark.input) + if (benchmark.compile) { + FJS(benchmark.schema, benchmark.options)(benchmark.input) + } else { + stringify(benchmark.input) + } }).run().then(() => { const task = bench.tasks[0] const hz = task.result.throughput.mean // ops/sec diff --git a/benchmark/bench.js b/benchmark/bench.js index acb87b94..7a995a4e 100644 --- a/benchmark/bench.js +++ b/benchmark/bench.js @@ -300,6 +300,92 @@ const benchmarks = [ }, input: { firstName: 'Max', lastName: 'Power', age: 22 } }, + { + name: 'oneOf external refs compile and first stringify', + compile: true, + schema: { + title: 'External refs oneOf schema', + type: 'object', + properties: { + content: { + oneOf: [ + { $ref: 'BenchmarkFoo#' }, + { $ref: 'BenchmarkBar#' }, + { $ref: 'BenchmarkBaz#' } + ] + } + } + }, + options: { + schema: { + BenchmarkFoo: { + type: 'object', + properties: { + kind: { const: 'foo' }, + title: { type: 'string' }, + count: { type: 'integer' }, + flags: { + type: 'array', + items: { type: 'string' } + } + }, + required: ['kind', 'title', 'count', 'flags'], + additionalProperties: false + }, + BenchmarkBar: { + type: 'object', + properties: { + kind: { const: 'bar' }, + name: { type: 'string' }, + value: { type: 'number' }, + meta: { + type: 'object', + properties: { + active: { type: 'boolean' }, + tags: { + type: 'array', + items: { type: 'string' } + } + }, + required: ['active', 'tags'], + additionalProperties: false + } + }, + required: ['kind', 'name', 'value', 'meta'], + additionalProperties: false + }, + BenchmarkBaz: { + type: 'object', + properties: { + kind: { const: 'baz' }, + id: { type: 'string' }, + nested: { + type: 'object', + properties: { + score: { type: 'number' }, + note: { type: 'string' } + }, + required: ['score', 'note'], + additionalProperties: false + } + }, + required: ['kind', 'id', 'nested'], + additionalProperties: false + } + } + }, + input: { + content: { + kind: 'bar', + name: 'benchmark', + value: 42.5, + meta: { + active: true, + tags: ['one', 'two', 'three'] + } + } + } + }, { name: 'object with const string property', schema: {