Skip to content

Commit 98b842d

Browse files
authored
Add method to SpecificationVersion to determine from schema node (#1221)
1 parent c98d19a commit 98b842d

File tree

5 files changed

+123
-1
lines changed

5 files changed

+123
-1
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,7 @@ The specification allows for the `$schema` keyword not to be specified, in which
377377
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.
378378

379379
```java
380-
SchemaRegistry registry = SchemaRegistry.builder().dialectRegistry(new BasicDialectRegistry(Dialects.getDraft202012())).build();
380+
SchemaRegistry registry = SchemaRegistry.withDefaultDialectId(null);
381381
```
382382

383383
### Results and output formats

src/main/java/com/networknt/schema/SchemaRegistry.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,26 @@ public static SchemaRegistry withDefaultDialect(SpecificationVersion specificati
282282
* Creates a new schema registry with a default schema dialect. The schema
283283
* dialect will only be used if the input does not specify a $schema.
284284
* <p>
285+
* If the dialectId is null then the $schema is mandatory.
286+
* <p>
287+
* This uses a dialect registry that contains all the supported standard
288+
* specification dialects, Draft 4, Draft 6, Draft 7, Draft 2019-09 and Draft
289+
* 2020-12.
290+
*
291+
* @param dialectId the default dialect id used when the schema does not
292+
* specify the $schema keyword
293+
* @return the factory
294+
*/
295+
public static SchemaRegistry withDefaultDialectId(String dialectId) {
296+
return withDefaultDialectId(dialectId, null);
297+
}
298+
299+
/**
300+
* Creates a new schema registry with a default schema dialect. The schema
301+
* dialect will only be used if the input does not specify a $schema.
302+
* <p>
303+
* If the dialectId is null then the $schema is mandatory.
304+
* <p>
285305
* This uses a dialect registry that contains all the supported standard
286306
* specification dialects, Draft 4, Draft 6, Draft 7, Draft 2019-09 and Draft
287307
* 2020-12.

src/main/java/com/networknt/schema/SpecificationVersion.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919

2020
import com.networknt.schema.dialect.DialectId;
2121

22+
import tools.jackson.databind.JsonNode;
23+
2224
/**
2325
* The version of the JSON Schema specification that defines the standard
2426
* dialects.
@@ -90,4 +92,20 @@ public static Optional<SpecificationVersion> fromDialectId(String dialectId) {
9092
}
9193
return Optional.empty();
9294
}
95+
96+
/**
97+
* Gets the specification version that matches the dialect id indicated by
98+
* $schema keyword. The dialect id is an IRI that identifies the meta schema
99+
* used to validate the dialect.
100+
*
101+
* @param schemaNode the schema
102+
* @return the specification version if it matches the dialect id
103+
*/
104+
public static Optional<SpecificationVersion> fromSchemaNode(JsonNode schemaNode) {
105+
JsonNode schema = schemaNode.get("$schema");
106+
if (schema != null && schema.isString()) {
107+
return fromDialectId(schema.stringValue());
108+
}
109+
return Optional.empty();
110+
}
93111
}

src/test/java/com/networknt/schema/SchemaRegistryTest.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919

2020
import java.util.ArrayList;
2121
import java.util.Collections;
22+
import java.util.HashMap;
2223
import java.util.List;
24+
import java.util.Map;
2325
import java.util.concurrent.CountDownLatch;
2426
import java.util.concurrent.atomic.AtomicBoolean;
2527

@@ -108,6 +110,14 @@ void noDefaultDialectButSchemaSpecified() {
108110
});
109111
}
110112

113+
@Test
114+
void noDefaultDialectWithDialectId() {
115+
SchemaRegistry registry = SchemaRegistry.withDefaultDialectId(null);
116+
assertThrows(MissingSchemaKeywordException.class, () -> {
117+
registry.getSchema("{\"type\":\"object\"}");
118+
});
119+
}
120+
111121
@Test
112122
void noDefaultDialectButSchemaSpecifiedButNotInRegistry() {
113123
SchemaRegistry registry = SchemaRegistry.builder()
@@ -116,4 +126,37 @@ void noDefaultDialectButSchemaSpecifiedButNotInRegistry() {
116126
registry.getSchema("{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"type\":\"object\"}");
117127
});
118128
}
129+
130+
@Test
131+
void noDialectReferredByParentShouldDefaultToDefaultDialect() {
132+
String schema = "{\r\n"
133+
+ " \"type\": \"object\",\r\n"
134+
+ " \"properties\": {\r\n"
135+
+ " \"key\": {\r\n"
136+
+ " \"type\": \"string\",\r\n"
137+
+ " \"description\": \"The unique identifier or name (key) for the pair.\"\r\n"
138+
+ " },\r\n"
139+
+ " \"value\": {\r\n"
140+
+ " \"type\": \"string\",\r\n"
141+
+ " \"description\": \"The associated data (value) for the key.\"\r\n"
142+
+ " }\r\n"
143+
+ " },\r\n"
144+
+ " \"required\": [\r\n"
145+
+ " \"key\",\r\n"
146+
+ " \"value\"\r\n"
147+
+ " ],\r\n"
148+
+ " \"additionalProperties\": false\r\n"
149+
+ "}";
150+
Map<String, String> schemas = new HashMap<>();
151+
schemas.put("https://example.org/schema", schema);
152+
SchemaRegistry registry = SchemaRegistry.withDefaultDialect(Dialects.getDraft4(), builder -> builder.schemas(schemas));
153+
Schema result = registry.getSchema("{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"type\":\"object\",\"$ref\":\"https://example.org/schema\"}");
154+
String input = "{\r\n"
155+
+ " \"key\": \"user_id\",\r\n"
156+
+ " \"value\": \"123456\"\r\n"
157+
+ "}";
158+
result.validate(input, InputFormat.JSON);
159+
Schema nested = registry.getSchema(SchemaLocation.of("https://example.org/schema"));
160+
assertEquals(Dialects.getDraft4(), nested.getSchemaContext().getDialect());
161+
}
119162
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright (c) 2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.networknt.schema;
17+
18+
import static org.junit.jupiter.api.Assertions.assertEquals;
19+
20+
import org.junit.jupiter.api.Test;
21+
22+
import tools.jackson.databind.JsonNode;
23+
import tools.jackson.databind.json.JsonMapper;
24+
25+
class SpecificationVersionTest {
26+
@Test
27+
void fromSchemaNode() {
28+
String schema = "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"type\":\"object\",\"$ref\":\"https://example.org/schema\"}";
29+
JsonNode schemaNode = JsonMapper.shared().readTree(schema);
30+
assertEquals(SpecificationVersionDetector.detectOptionalVersion(schemaNode, false),
31+
SpecificationVersion.fromSchemaNode(schemaNode));
32+
}
33+
34+
@Test
35+
void fromSchemaNodeMissing() {
36+
String schema = "{\"type\":\"object\",\"$ref\":\"https://example.org/schema\"}";
37+
JsonNode schemaNode = JsonMapper.shared().readTree(schema);
38+
assertEquals(SpecificationVersionDetector.detectOptionalVersion(schemaNode, false),
39+
SpecificationVersion.fromSchemaNode(schemaNode));
40+
}
41+
}

0 commit comments

Comments
 (0)