Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,7 @@ The specification allows for the `$schema` keyword not to be specified, in which
The following example creates a `SchemaRegistry` that does not specify a default dialect and will throw a `MissingSchemaKeywordException` if the schema does not specify a dialect using the `$schema` keyword.

```java
SchemaRegistry registry = SchemaRegistry.builder().dialectRegistry(new BasicDialectRegistry(Dialects.getDraft202012())).build();
SchemaRegistry registry = SchemaRegistry.withDefaultDialectId(null);
```

### Results and output formats
Expand Down
20 changes: 20 additions & 0 deletions src/main/java/com/networknt/schema/SchemaRegistry.java
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,26 @@ public static SchemaRegistry withDefaultDialect(SpecificationVersion specificati
* Creates a new schema registry with a default schema dialect. The schema
* dialect will only be used if the input does not specify a $schema.
* <p>
* If the dialectId is null then the $schema is mandatory.
* <p>
* This uses a dialect registry that contains all the supported standard
* specification dialects, Draft 4, Draft 6, Draft 7, Draft 2019-09 and Draft
* 2020-12.
*
* @param dialectId the default dialect id used when the schema does not
* specify the $schema keyword
* @return the factory
*/
public static SchemaRegistry withDefaultDialectId(String dialectId) {
return withDefaultDialectId(dialectId, null);
}

/**
* Creates a new schema registry with a default schema dialect. The schema
* dialect will only be used if the input does not specify a $schema.
* <p>
* If the dialectId is null then the $schema is mandatory.
* <p>
* This uses a dialect registry that contains all the supported standard
* specification dialects, Draft 4, Draft 6, Draft 7, Draft 2019-09 and Draft
* 2020-12.
Expand Down
18 changes: 18 additions & 0 deletions src/main/java/com/networknt/schema/SpecificationVersion.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@

import com.networknt.schema.dialect.DialectId;

import tools.jackson.databind.JsonNode;

/**
* The version of the JSON Schema specification that defines the standard
* dialects.
Expand Down Expand Up @@ -90,4 +92,20 @@ public static Optional<SpecificationVersion> fromDialectId(String dialectId) {
}
return Optional.empty();
}

/**
* Gets the specification version that matches the dialect id indicated by
* $schema keyword. The dialect id is an IRI that identifies the meta schema
* used to validate the dialect.
*
* @param schemaNode the schema
* @return the specification version if it matches the dialect id
*/
public static Optional<SpecificationVersion> fromSchemaNode(JsonNode schemaNode) {
JsonNode schema = schemaNode.get("$schema");
if (schema != null && schema.isString()) {
return fromDialectId(schema.stringValue());
}
return Optional.empty();
}
}
43 changes: 43 additions & 0 deletions src/test/java/com/networknt/schema/SchemaRegistryTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;

Expand Down Expand Up @@ -108,6 +110,14 @@ void noDefaultDialectButSchemaSpecified() {
});
}

@Test
void noDefaultDialectWithDialectId() {
SchemaRegistry registry = SchemaRegistry.withDefaultDialectId(null);
assertThrows(MissingSchemaKeywordException.class, () -> {
registry.getSchema("{\"type\":\"object\"}");
});
}

@Test
void noDefaultDialectButSchemaSpecifiedButNotInRegistry() {
SchemaRegistry registry = SchemaRegistry.builder()
Expand All @@ -116,4 +126,37 @@ void noDefaultDialectButSchemaSpecifiedButNotInRegistry() {
registry.getSchema("{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"type\":\"object\"}");
});
}

@Test
void noDialectReferredByParentShouldDefaultToDefaultDialect() {
String schema = "{\r\n"
+ " \"type\": \"object\",\r\n"
+ " \"properties\": {\r\n"
+ " \"key\": {\r\n"
+ " \"type\": \"string\",\r\n"
+ " \"description\": \"The unique identifier or name (key) for the pair.\"\r\n"
+ " },\r\n"
+ " \"value\": {\r\n"
+ " \"type\": \"string\",\r\n"
+ " \"description\": \"The associated data (value) for the key.\"\r\n"
+ " }\r\n"
+ " },\r\n"
+ " \"required\": [\r\n"
+ " \"key\",\r\n"
+ " \"value\"\r\n"
+ " ],\r\n"
+ " \"additionalProperties\": false\r\n"
+ "}";
Map<String, String> schemas = new HashMap<>();
schemas.put("https://example.org/schema", schema);
SchemaRegistry registry = SchemaRegistry.withDefaultDialect(Dialects.getDraft4(), builder -> builder.schemas(schemas));
Schema result = registry.getSchema("{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"type\":\"object\",\"$ref\":\"https://example.org/schema\"}");
String input = "{\r\n"
+ " \"key\": \"user_id\",\r\n"
+ " \"value\": \"123456\"\r\n"
+ "}";
result.validate(input, InputFormat.JSON);
Schema nested = registry.getSchema(SchemaLocation.of("https://example.org/schema"));
assertEquals(Dialects.getDraft4(), nested.getSchemaContext().getDialect());
}
}
41 changes: 41 additions & 0 deletions src/test/java/com/networknt/schema/SpecificationVersionTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright (c) 2025 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 org.junit.jupiter.api.Test;

import tools.jackson.databind.JsonNode;
import tools.jackson.databind.json.JsonMapper;

class SpecificationVersionTest {
@Test
void fromSchemaNode() {
String schema = "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"type\":\"object\",\"$ref\":\"https://example.org/schema\"}";
JsonNode schemaNode = JsonMapper.shared().readTree(schema);
assertEquals(SpecificationVersionDetector.detectOptionalVersion(schemaNode, false),
SpecificationVersion.fromSchemaNode(schemaNode));
}

@Test
void fromSchemaNodeMissing() {
String schema = "{\"type\":\"object\",\"$ref\":\"https://example.org/schema\"}";
JsonNode schemaNode = JsonMapper.shared().readTree(schema);
assertEquals(SpecificationVersionDetector.detectOptionalVersion(schemaNode, false),
SpecificationVersion.fromSchemaNode(schemaNode));
}
}