From 313b193dc7c179f532128b83c6bb56f6ad59dd83 Mon Sep 17 00:00:00 2001 From: Steve Hu Date: Sat, 20 Jun 2026 18:27:30 -0400 Subject: [PATCH] fixes #1252 problem with additionalProperties schema in jsconfig schema --- .../AdditionalPropertiesValidator.java | 8 -- .../com/networknt/schema/Issue1252Test.java | 93 +++++++++++++++++++ 2 files changed, 93 insertions(+), 8 deletions(-) create mode 100644 src/test/java/com/networknt/schema/Issue1252Test.java diff --git a/src/main/java/com/networknt/schema/keyword/AdditionalPropertiesValidator.java b/src/main/java/com/networknt/schema/keyword/AdditionalPropertiesValidator.java index 1780d70a8..fa8f62914 100644 --- a/src/main/java/com/networknt/schema/keyword/AdditionalPropertiesValidator.java +++ b/src/main/java/com/networknt/schema/keyword/AdditionalPropertiesValidator.java @@ -109,10 +109,6 @@ protected void validate(ExecutionContext executionContext, JsonNode node, JsonNo for (Iterator> it = node.properties().iterator(); it.hasNext(); ) { Entry entry = it.next(); String pname = entry.getKey(); - // skip the context items - if (pname.startsWith("#")) { - continue; - } if (!allowedProperties.contains(pname) && !handledByPatternProperties(pname)) { if (!allowAdditionalProperties) { executionContext.addError(error().instanceNode(node).property(pname) @@ -156,10 +152,6 @@ public void walk(ExecutionContext executionContext, JsonNode node, JsonNode root // Else continue walking. for (Iterator it = node.propertyNames().iterator(); it.hasNext(); ) { String pname = it.next(); - // skip the context items - if (pname.startsWith("#")) { - continue; - } if (!allowedProperties.contains(pname) && !handledByPatternProperties(pname)) { if (allowAdditionalProperties) { if (additionalPropertiesSchema != null) { diff --git a/src/test/java/com/networknt/schema/Issue1252Test.java b/src/test/java/com/networknt/schema/Issue1252Test.java new file mode 100644 index 000000000..05f71d821 --- /dev/null +++ b/src/test/java/com/networknt/schema/Issue1252Test.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2026 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +class Issue1252Test { + @Test + void additionalPropertiesSchemaValidatesHashPrefixedProperties() { + String schemaData = "{\r\n" + + " \"$schema\": \"http://json-schema.org/draft-07/schema#\",\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"compilerOptions\": {\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"paths\": {\r\n" + + " \"type\": \"object\",\r\n" + + " \"additionalProperties\": {\r\n" + + " \"type\": \"array\",\r\n" + + " \"items\": {\r\n" + + " \"type\": \"string\"\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + String inputData = "{\r\n" + + " \"compilerOptions\": {\r\n" + + " \"paths\": {\r\n" + + " \"#routes/*\": [\r\n" + + " {\r\n" + + " \"invalid\": null\r\n" + + " }\r\n" + + " ]\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7).getSchema(schemaData); + List errors = schema.validate(inputData, InputFormat.JSON); + + assertFalse(errors.isEmpty()); + Error error = errors.get(0); + assertEquals("/compilerOptions/paths/#routes~1*/0", error.getInstanceLocation().toString()); + assertEquals("/properties/compilerOptions/properties/paths/additionalProperties/items/type", + error.getEvaluationPath().toString()); + assertEquals("/compilerOptions/paths/#routes~1*/0: object found, string expected", error.toString()); + } + + @Test + void additionalPropertiesFalseRejectsHashPrefixedProperties() { + String schemaData = "{\r\n" + + " \"$schema\": \"http://json-schema.org/draft-07/schema#\",\r\n" + + " \"type\": \"object\",\r\n" + + " \"additionalProperties\": false\r\n" + + "}"; + String inputData = "{\r\n" + + " \"#plugins/\": [\r\n" + + " \"plugin\"\r\n" + + " ]\r\n" + + "}"; + + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7).getSchema(schemaData); + List errors = schema.validate(inputData, InputFormat.JSON); + + assertFalse(errors.isEmpty()); + Error error = errors.get(0); + assertEquals("", error.getInstanceLocation().toString()); + assertEquals("/additionalProperties", error.getEvaluationPath().toString()); + assertEquals("#plugins/", error.getProperty()); + } +}