From ae610b44427d6c63b6bda446eba91e946020217c Mon Sep 17 00:00:00 2001 From: Jacob Quinn Date: Sun, 4 Oct 2026 11:55:23 -0600 Subject: [PATCH 1/2] Reuse property patterns during object validation Compile pattern coverage once per validation call after the first field without a literal property, and check coverage without materializing match objects. Preserve public matching and error-path behavior and bound allocations on wide objects. AI disclosure: This work was prepared with assistance from OpenAI Codex. --- src/validation.jl | 20 ++++++++-- test/additional_patterns.jl | 78 +++++++++++++++++++++++++++++++++++++ test/runtests.jl | 2 + 3 files changed, 96 insertions(+), 4 deletions(-) create mode 100644 test/additional_patterns.jl diff --git a/src/validation.jl b/src/validation.jl index cc1e1d1..332e791 100644 --- a/src/validation.jl +++ b/src/validation.jl @@ -489,9 +489,15 @@ function _validate( ) properties = get(schema, "properties", Dict{String,Any}()) patternProperties = get(schema, "patternProperties", Dict{String,Any}()) + patterns = nothing for (k, v) in x - if k in keys(properties) || - any(r -> match(Regex(r), k) !== nothing, keys(patternProperties)) + if k in keys(properties) + continue + end + if patterns === nothing + patterns = [Regex(r) for r in keys(patternProperties)] + end + if any(r -> occursin(r, k), patterns) continue end ret = _validate_child(v, val, path, string(k)) @@ -514,9 +520,15 @@ function _validate( end properties = get(schema, "properties", Dict{String,Any}()) patternProperties = get(schema, "patternProperties", Dict{String,Any}()) + patterns = nothing for (k, v) in x - if k in keys(properties) || - any(r -> match(Regex(r), k) !== nothing, keys(patternProperties)) + if k in keys(properties) + continue + end + if patterns === nothing + patterns = [Regex(r) for r in keys(patternProperties)] + end + if any(r -> occursin(r, k), patterns) continue end return SingleIssue(x, path, "additionalProperties", val) diff --git a/test/additional_patterns.jl b/test/additional_patterns.jl new file mode 100644 index 0000000..0bb7cbc --- /dev/null +++ b/test/additional_patterns.jl @@ -0,0 +1,78 @@ +@testset "Additional properties with pattern coverage" begin + properties = Dict("literal" => Dict("type" => "integer")) + patterns = Dict( + "^covered_" => Dict("type" => "string"), + "middle" => Dict("type" => "boolean"), + "^π" => true, + ) + for extra in (false, true, Dict("type" => "integer")) + schema = JSONSchema.Schema( + Dict( + "properties" => properties, + "patternProperties" => patterns, + "additionalProperties" => extra, + ), + ) + for data in ( + Dict{String,Any}(), + Dict("literal" => 2), + Dict("covered_name" => "ok"), + Dict("xmiddley" => true), + Dict("πname" => nothing), + Dict{String,Any}( + "literal" => 2, + "covered_name" => "ok", + "xmiddley" => true, + ), + ) + @test JSONSchema.validate(schema, data) === nothing + end + @test JSONSchema.validate(schema, Dict("covered_middle" => "ok")) isa + JSONSchema.SingleIssue + @test JSONSchema.validate(schema, Dict("literal" => false)) isa + JSONSchema.SingleIssue + @test isvalid(schema, Dict("unknown" => 2)) == (extra !== false) + @test isvalid(schema, Dict("unknown" => false)) == (extra === true) + if extra isa AbstractDict + issue = JSONSchema.validate(schema, Dict("unknown" => false)) + @test issue.reason == "type" + @test issue.path == "[unknown]" + @test JSONSchema.json_pointer(issue) == "#/unknown" + elseif extra === false + issue = JSONSchema.validate(schema, Dict("unknown" => 2)) + @test issue.reason == "additionalProperties" + @test JSONSchema.json_pointer(issue) == "#" + end + end + + schema = JSONSchema.Schema( + Dict( + "items" => Dict( + "patternProperties" => Dict("^covered_" => true), + "additionalProperties" => Dict("type" => "integer"), + ), + ), + ) + @test isvalid(schema, [Dict("covered_name" => nothing), Dict("other" => 3)]) + issue = JSONSchema.validate( + schema, + [Dict("covered_name" => nothing), Dict("other" => "bad")], + ) + @test issue.reason == "type" + @test JSONSchema.json_pointer(issue) == "#/1/other" + + data = Dict{String,Any}("bucket_$(mod1(i, 5))_$i" => i for i in 1:2500) + for extra in (false, Dict("type" => "integer")) + schema = JSONSchema.Schema( + Dict( + "patternProperties" => + Dict("^bucket_$(i)_" => true for i in 1:5), + "additionalProperties" => extra, + ), + ) + for _ in 1:3 + @test JSONSchema.validate(schema, data) === nothing + end + @test (@allocated JSONSchema.validate(schema, data)) < 800_000 + end +end diff --git a/test/runtests.jl b/test/runtests.jl index 26c0971..3e3f654 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -12,6 +12,8 @@ import JSON3 import OrderedCollections import ZipFile +include("additional_patterns.jl") + const TEST_SUITE_URL = "https://github.com/json-schema-org/JSON-Schema-Test-Suite/archive/23.1.0.zip" const SCHEMA_TEST_DIR = let From d4516f6f5cc87e0f50bff11bbddf8223298e0a94 Mon Sep 17 00:00:00 2001 From: Jacob Quinn Date: Sun, 4 Oct 2026 12:13:16 -0600 Subject: [PATCH 2/2] Avoid unused match results when validating pattern properties Use the Boolean regex predicate in the remaining pattern-property existence check; nested validation does not use captures. All public and full-package tests pass on the minimum and current Julia versions. AI disclosure: This work was prepared with assistance from OpenAI Codex. --- src/validation.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/validation.jl b/src/validation.jl index 332e791..cf3fdf8 100644 --- a/src/validation.jl +++ b/src/validation.jl @@ -467,7 +467,7 @@ function _validate( for (k_val, v_val) in val r = Regex(k_val) for (k_x, v_x) in x - if match(r, k_x) === nothing + if !occursin(r, k_x) continue end ret = _validate_child(v_x, v_val, path, string(k_x))