summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJustin Tay <49700559+justin-tay@users.noreply.github.com>2024-03-12 20:55:14 +0800
committerGitHub <noreply@github.com>2024-03-12 08:55:14 -0400
commit0f983b05ed7478c55db1dc0d820f60f0becaf787 (patch)
treedba6e09a32f66904895afdf9db35dfee52470cfa
parenteea61d6b630ee1277371bf7ffc0e171ac4cbff54 (diff)
downloadjson-schema-validator-0f983b05ed7478c55db1dc0d820f60f0becaf787.tar.gz
Refactor walk (#986)
* Allow apply defaults to resolve ref and refactor walk listeners * Refactor * Refactor * Refactor * Refactor * Refactor * Refactor * Refactor * Update docs * Refactor * Refactor * Refactor * Add test
-rw-r--r--doc/upgrading.md25
-rw-r--r--doc/walkers.md85
-rw-r--r--src/main/java/com/networknt/schema/ConstValidator.java5
-rw-r--r--src/main/java/com/networknt/schema/DynamicRefValidator.java2
-rw-r--r--src/main/java/com/networknt/schema/ItemsValidator.java27
-rw-r--r--src/main/java/com/networknt/schema/ItemsValidator202012.java26
-rw-r--r--src/main/java/com/networknt/schema/JsonSchema.java78
-rw-r--r--src/main/java/com/networknt/schema/MaxPropertiesValidator.java4
-rw-r--r--src/main/java/com/networknt/schema/MinPropertiesValidator.java4
-rw-r--r--src/main/java/com/networknt/schema/PrefixItemsValidator.java26
-rw-r--r--src/main/java/com/networknt/schema/PropertiesValidator.java47
-rw-r--r--src/main/java/com/networknt/schema/RecursiveRefValidator.java2
-rw-r--r--src/main/java/com/networknt/schema/RefValidator.java2
-rw-r--r--src/main/java/com/networknt/schema/TypeValidator.java10
-rw-r--r--src/main/java/com/networknt/schema/UniqueItemsValidator.java4
-rw-r--r--src/main/java/com/networknt/schema/utils/JsonNodes.java82
-rw-r--r--src/main/java/com/networknt/schema/utils/JsonSchemaRefs.java49
-rw-r--r--src/main/java/com/networknt/schema/walk/AbstractWalkListenerRunner.java15
-rw-r--r--src/main/java/com/networknt/schema/walk/DefaultItemWalkListenerRunner.java24
-rw-r--r--src/main/java/com/networknt/schema/walk/DefaultKeywordWalkListenerRunner.java19
-rw-r--r--src/main/java/com/networknt/schema/walk/DefaultPropertyWalkListenerRunner.java20
-rw-r--r--src/main/java/com/networknt/schema/walk/WalkEvent.java138
-rw-r--r--src/main/java/com/networknt/schema/walk/WalkListenerRunner.java16
-rw-r--r--src/test/java/com/networknt/schema/Issue451Test.java2
-rw-r--r--src/test/java/com/networknt/schema/Issue467Test.java6
-rw-r--r--src/test/java/com/networknt/schema/Issue724Test.java4
-rw-r--r--src/test/java/com/networknt/schema/JsonWalkTest.java12
-rw-r--r--src/test/java/com/networknt/schema/walk/JsonSchemaWalkListenerTest.java814
28 files changed, 1250 insertions, 298 deletions
diff --git a/doc/upgrading.md b/doc/upgrading.md
index a72ad22..506e967 100644
--- a/doc/upgrading.md
+++ b/doc/upgrading.md
@@ -6,7 +6,30 @@ This contains information on the notable or breaking changes in each version.
### 1.4.0
-This contains breaking changes in how custom meta-schemas are created.
+This contains breaking changes
+- to those using the walk functionality
+- in how custom meta-schemas are created
+
+When using the walker with defaults the `default` across a `$ref` are properly resolved and used.
+
+The behavior for the property listener is now more consistent whether or not validation is enabled. Previously if validation is enabled but the property is `null` the property listener is not called while if validation is not enabled it will be called. Now the property listener will be called in both scenarios.
+
+The following are the breaking changes to those using the walk functionality.
+
+`WalkEvent`
+| Field | Change | Notes
+|--------------------------|--------------|----------
+| `schemaLocation` | Removed | For keywords: `getValidator().getSchemaLocation()`. For items and properties: `getSchema().getSchemaLocation()`
+| `evaluationPath` | Removed | For keywords: `getValidator().getEvaluationPath()`. For items and properties: `getSchema().getEvaluationPath()`
+| `schemaNode` | Removed | `getSchema().getSchemaNode()`
+| `parentSchema` | Removed | `getSchema().getParentSchema()`
+| `schema` | New | For keywords this is the parent schema of the validator. For items and properties this is the item or property schema being evaluated.
+| `node` | Renamed | `instanceNode`
+| `currentJsonSchemaFactory`| Removed | `getSchema().getValidationContext().getJsonSchemaFactory()`
+| `validator` | New | The validator indicated by the keyword.
+
+
+The following are the breaking changes in how custom meta-schemas are created.
`JsonSchemaFactory`
* The following were renamed on `JsonSchemaFactory` builder
diff --git a/doc/walkers.md b/doc/walkers.md
index b06fb12..51736e4 100644
--- a/doc/walkers.md
+++ b/doc/walkers.md
@@ -38,65 +38,61 @@ public interface JsonSchemaWalker {
The JSONValidator interface extends this new interface thus allowing all the validator's defined in library to implement this new interface. BaseJsonValidator class provides a default implementation of the walk method. In this case the walk method does nothing but validating based on shouldValidateSchema parameter.
```java
-/**
+ /**
* This is default implementation of walk method. Its job is to call the
* validate method if shouldValidateSchema is enabled.
*/
@Override
- public Set<ValidationMessage> walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode,
- JsonNodePath instanceLocation, boolean shouldValidateSchema);
- Set<ValidationMessage> validationMessages = new LinkedHashSet<ValidationMessage>();
- if (shouldValidateSchema) {
- validationMessages = validate(executionContext, node, rootNode, instanceLocation);
- }
- return validationMessages;
+ default Set<ValidationMessage> walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode,
+ JsonNodePath instanceLocation, boolean shouldValidateSchema) {
+ return shouldValidateSchema ? validate(executionContext, node, rootNode, instanceLocation)
+ : Collections.emptySet();
}
-
```
A new walk method added to the JSONSchema class allows us to walk through the JSONSchema.
```java
- public ValidationResult walk(JsonNode node, boolean shouldValidateSchema) {
- // Create the collector context object.
- CollectorContext collectorContext = new CollectorContext();
- // Set the collector context in thread info, this is unique for every thread.
- ThreadInfo.set(CollectorContext.COLLECTOR_CONTEXT_THREAD_LOCAL_KEY, collectorContext);
- Set<ValidationMessage> errors = walk(node, node, AT_ROOT, shouldValidateSchema);
- // Load all the data from collectors into the context.
- collectorContext.loadCollectors();
- // Collect errors and collector context into validation result.
- ValidationResult validationResult = new ValidationResult(errors, collectorContext);
- return validationResult;
+ public ValidationResult walk(JsonNode node, boolean validate) {
+ return walk(createExecutionContext(), node, validate);
}
@Override
- public Set<ValidationMessage> walk(JsonNode node, JsonNode rootNode, String at, boolean shouldValidateSchema) {
- Set<ValidationMessage> validationMessages = new LinkedHashSet<ValidationMessage>();
+ public Set<ValidationMessage> walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode,
+ JsonNodePath instanceLocation, boolean shouldValidateSchema) {
+ Set<ValidationMessage> errors = new LinkedHashSet<>();
// Walk through all the JSONWalker's.
- for (Entry<String, JsonValidator> entry : validators.entrySet()) {
- JsonWalker jsonWalker = entry.getValue();
- String schemaPathWithKeyword = entry.getKey();
+ for (JsonValidator validator : getValidators()) {
+ JsonNodePath evaluationPathWithKeyword = validator.getEvaluationPath();
try {
- // Call all the pre-walk listeners. If all the pre-walk listeners return true
- // then continue to walk method.
- if (keywordWalkListenerRunner.runPreWalkListeners(schemaPathWithKeyword, node, rootNode, at, schemaPath,
- schemaNode, parentSchema)) {
- validationMessages.addAll(jsonWalker.walk(node, rootNode, at, shouldValidateSchema));
+ // Call all the pre-walk listeners. If at least one of the pre walk listeners
+ // returns SKIP, then skip the walk.
+ if (this.validationContext.getConfig().getKeywordWalkListenerRunner().runPreWalkListeners(executionContext,
+ evaluationPathWithKeyword.getName(-1), node, rootNode, instanceLocation,
+ this, validator)) {
+ Set<ValidationMessage> results = null;
+ try {
+ results = validator.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema);
+ } finally {
+ if (results != null && !results.isEmpty()) {
+ errors.addAll(results);
+ }
+ }
}
} finally {
// Call all the post-walk listeners.
- keywordWalkListenerRunner.runPostWalkListeners(schemaPathWithKeyword, node, rootNode, at, schemaPath,
- schemaNode, parentSchema, validationMessages);
+ this.validationContext.getConfig().getKeywordWalkListenerRunner().runPostWalkListeners(executionContext,
+ evaluationPathWithKeyword.getName(-1), node, rootNode, instanceLocation,
+ this, validator, errors);
}
}
- return validationMessages;
+ return errors;
}
```
Following code snippet shows how to call the walk method on a JsonSchema instance.
-```
-ValidationResult result = jsonSchema.walk(data,false);
+```java
+ValidationResult result = jsonSchema.walk(data, false);
```
@@ -122,7 +118,7 @@ private static class PropertiesKeywordListener implements JsonSchemaWalkListener
@Override
public WalkFlow onWalkStart(WalkEvent keywordWalkEvent) {
- JsonNode schemaNode = keywordWalkEvent.getSchemaNode();
+ JsonNode schemaNode = keywordWalkEvent.getSchema().getSchemaNode();
if (schemaNode.get("title").textValue().equals("Property3")) {
return WalkFlow.SKIP;
}
@@ -146,7 +142,7 @@ SchemaValidatorsConfig schemaValidatorsConfig = new SchemaValidatorsConfig();
schemaValidatorsConfig.addKeywordWalkListener(ValidatorTypeCode.PROPERTIES.getValue(),
new PropertiesKeywordListener());
final JsonSchemaFactory schemaFactory = JsonSchemaFactory
- .builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909)).addMetaSchema(metaSchema)
+ .builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909)).metaSchema(metaSchema)
.build();
this.jsonSchema = schemaFactory.getSchema(getSchema(), schemaValidatorsConfig);
@@ -160,7 +156,7 @@ Both property walk listeners and keyword walk listener can be modeled by using t
SchemaValidatorsConfig schemaValidatorsConfig = new SchemaValidatorsConfig();
schemaValidatorsConfig.addPropertyWalkListener(new ExamplePropertyWalkListener());
final JsonSchemaFactory schemaFactory = JsonSchemaFactory
- .builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909)).addMetaSchema(metaSchema)
+ .builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909)).metaSchema(metaSchema)
.build();
this.jsonSchema = schemaFactory.getSchema(getSchema(), schemaValidatorsConfig);
@@ -176,16 +172,15 @@ Following snippet shows the details captured by WalkEvent instance.
```java
public class WalkEvent {
-
private ExecutionContext executionContext;
- private SchemaLocation schemaLocation;
- private JsonNodePath evaluationPath;
- private JsonNode schemaNode;
- private JsonSchema parentSchema;
+ private JsonSchema schema;
private String keyword;
- private JsonNode node;
private JsonNode rootNode;
+ private JsonNode instanceNode;
private JsonNodePath instanceLocation;
+ private JsonValidator validator;
+ ...
+}
```
### Sample Flow
@@ -285,7 +280,7 @@ But if we apply defaults while walking, then required validation passes, and the
JsonSchemaFactory schemaFactory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4);
SchemaValidatorsConfig schemaValidatorsConfig = new SchemaValidatorsConfig();
schemaValidatorsConfig.setApplyDefaultsStrategy(new ApplyDefaultsStrategy(true, true, true));
- JsonSchema jsonSchema = schemaFactory.getSchema(getClass().getClassLoader().getResourceAsStream("schema.json"), schemaValidatorsConfig);
+ JsonSchema jsonSchema = schemaFactory.getSchema(SchemaLocation.of("classpath:schema.json"), schemaValidatorsConfig);
JsonNode inputNode = objectMapper.readTree(getClass().getClassLoader().getResourceAsStream("data.json"));
ValidationResult result = jsonSchema.walk(inputNode, true);
diff --git a/src/main/java/com/networknt/schema/ConstValidator.java b/src/main/java/com/networknt/schema/ConstValidator.java
index a06f16c..291144b 100644
--- a/src/main/java/com/networknt/schema/ConstValidator.java
+++ b/src/main/java/com/networknt/schema/ConstValidator.java
@@ -27,11 +27,10 @@ import java.util.Set;
*/
public class ConstValidator extends BaseJsonValidator implements JsonValidator {
private static final Logger logger = LoggerFactory.getLogger(ConstValidator.class);
- JsonNode schemaNode;
- public ConstValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+ public ConstValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode,
+ JsonSchema parentSchema, ValidationContext validationContext) {
super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.CONST, validationContext);
- this.schemaNode = schemaNode;
}
public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
diff --git a/src/main/java/com/networknt/schema/DynamicRefValidator.java b/src/main/java/com/networknt/schema/DynamicRefValidator.java
index a113d11..6f72993 100644
--- a/src/main/java/com/networknt/schema/DynamicRefValidator.java
+++ b/src/main/java/com/networknt/schema/DynamicRefValidator.java
@@ -28,7 +28,7 @@ import java.util.*;
public class DynamicRefValidator extends BaseJsonValidator {
private static final Logger logger = LoggerFactory.getLogger(DynamicRefValidator.class);
- protected JsonSchemaRef schema;
+ protected final JsonSchemaRef schema;
public DynamicRefValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.DYNAMIC_REF, validationContext);
diff --git a/src/main/java/com/networknt/schema/ItemsValidator.java b/src/main/java/com/networknt/schema/ItemsValidator.java
index 1905886..7aede16 100644
--- a/src/main/java/com/networknt/schema/ItemsValidator.java
+++ b/src/main/java/com/networknt/schema/ItemsValidator.java
@@ -19,6 +19,7 @@ package com.networknt.schema;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.networknt.schema.annotation.JsonNodeAnnotation;
+import com.networknt.schema.utils.JsonSchemaRefs;
import com.networknt.schema.utils.SetView;
import org.slf4j.Logger;
@@ -35,7 +36,7 @@ public class ItemsValidator extends BaseJsonValidator {
private final JsonSchema schema;
private final List<JsonSchema> tupleSchema;
- private Boolean additionalItems;
+ private final Boolean additionalItems;
private final JsonSchema additionalSchema;
private Boolean hasUnevaluatedItemsValidator = null;
@@ -47,6 +48,8 @@ public class ItemsValidator extends BaseJsonValidator {
public ItemsValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.ITEMS, validationContext);
+ Boolean additionalItems = null;
+
this.tupleSchema = new ArrayList<>();
JsonSchema foundSchema = null;
JsonSchema foundAdditionalSchema = null;
@@ -66,7 +69,7 @@ public class ItemsValidator extends BaseJsonValidator {
if (addItemNode != null) {
additionalItemsSchemaNode = addItemNode;
if (addItemNode.isBoolean()) {
- this.additionalItems = addItemNode.asBoolean();
+ additionalItems = addItemNode.asBoolean();
} else if (addItemNode.isObject()) {
foundAdditionalSchema = validationContext.newSchema(
parentSchema.schemaLocation.append(PROPERTY_ADDITIONAL_ITEMS),
@@ -74,6 +77,7 @@ public class ItemsValidator extends BaseJsonValidator {
}
}
}
+ this.additionalItems = additionalItems;
this.schema = foundSchema;
this.additionalSchema = foundAdditionalSchema;
this.additionalItemsEvaluationPath = parentSchema.evaluationPath.append(PROPERTY_ADDITIONAL_ITEMS);
@@ -205,7 +209,7 @@ public class ItemsValidator extends BaseJsonValidator {
JsonNode defaultNode = null;
if (this.validationContext.getConfig().getApplyDefaultsStrategy().shouldApplyArrayDefaults()
&& this.schema != null) {
- defaultNode = this.schema.getSchemaNode().get("default");
+ defaultNode = getDefaultNode(this.schema);
}
int i = 0;
for (JsonNode n : arrayNode) {
@@ -222,6 +226,17 @@ public class ItemsValidator extends BaseJsonValidator {
return validationMessages;
}
+ private static JsonNode getDefaultNode(JsonSchema schema) {
+ JsonNode result = schema.getSchemaNode().get("default");
+ if (result == null) {
+ JsonSchemaRef schemaRef = JsonSchemaRefs.from(schema);
+ if (schemaRef != null) {
+ result = getDefaultNode(schemaRef.getSchema());
+ }
+ }
+ return result;
+ }
+
private void doWalk(ExecutionContext executionContext, HashSet<ValidationMessage> validationMessages, int i, JsonNode node,
JsonNode rootNode, JsonNodePath instanceLocation, boolean shouldValidateSchema) {
if (this.schema != null) {
@@ -247,14 +262,12 @@ public class ItemsValidator extends BaseJsonValidator {
private void walkSchema(ExecutionContext executionContext, JsonSchema walkSchema, JsonNode node, JsonNode rootNode,
JsonNodePath instanceLocation, boolean shouldValidateSchema, Set<ValidationMessage> validationMessages) {
boolean executeWalk = this.validationContext.getConfig().getItemWalkListenerRunner().runPreWalkListeners(executionContext, ValidatorTypeCode.ITEMS.getValue(),
- node, rootNode, instanceLocation, walkSchema.getEvaluationPath(), walkSchema.getSchemaLocation(),
- walkSchema.getSchemaNode(), walkSchema.getParentSchema(), this.validationContext, this.validationContext.getJsonSchemaFactory());
+ node, rootNode, instanceLocation, walkSchema, this);
if (executeWalk) {
validationMessages.addAll(walkSchema.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema));
}
this.validationContext.getConfig().getItemWalkListenerRunner().runPostWalkListeners(executionContext, ValidatorTypeCode.ITEMS.getValue(), node, rootNode,
- instanceLocation, this.evaluationPath, walkSchema.getSchemaLocation(),
- walkSchema.getSchemaNode(), walkSchema.getParentSchema(), this.validationContext, this.validationContext.getJsonSchemaFactory(), validationMessages);
+ instanceLocation, walkSchema, this, validationMessages);
}
diff --git a/src/main/java/com/networknt/schema/ItemsValidator202012.java b/src/main/java/com/networknt/schema/ItemsValidator202012.java
index 196d474..a9cd02a 100644
--- a/src/main/java/com/networknt/schema/ItemsValidator202012.java
+++ b/src/main/java/com/networknt/schema/ItemsValidator202012.java
@@ -19,6 +19,7 @@ package com.networknt.schema;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.networknt.schema.annotation.JsonNodeAnnotation;
+import com.networknt.schema.utils.JsonSchemaRefs;
import com.networknt.schema.utils.SetView;
import org.slf4j.Logger;
@@ -119,7 +120,7 @@ public class ItemsValidator202012 extends BaseJsonValidator {
JsonNode defaultNode = null;
if (this.validationContext.getConfig().getApplyDefaultsStrategy().shouldApplyArrayDefaults()
&& this.schema != null) {
- defaultNode = this.schema.getSchemaNode().get("default");
+ defaultNode = getDefaultNode(this.schema);
}
for (int i = this.prefixCount; i < node.size(); ++i) {
JsonNode n = node.get(i);
@@ -139,6 +140,17 @@ public class ItemsValidator202012 extends BaseJsonValidator {
return validationMessages;
}
+ private static JsonNode getDefaultNode(JsonSchema schema) {
+ JsonNode result = schema.getSchemaNode().get("default");
+ if (result == null) {
+ JsonSchemaRef schemaRef = JsonSchemaRefs.from(schema);
+ if (schemaRef != null) {
+ result = getDefaultNode(schemaRef.getSchema());
+ }
+ }
+ return result;
+ }
+
private void walkSchema(ExecutionContext executionContext, JsonSchema walkSchema, JsonNode node, JsonNode rootNode,
JsonNodePath instanceLocation, boolean shouldValidateSchema, Set<ValidationMessage> validationMessages) {
//@formatter:off
@@ -148,10 +160,7 @@ public class ItemsValidator202012 extends BaseJsonValidator {
node,
rootNode,
instanceLocation,
- walkSchema.getEvaluationPath(),
- walkSchema.getSchemaLocation(),
- walkSchema.getSchemaNode(),
- walkSchema.getParentSchema(), this.validationContext, this.validationContext.getJsonSchemaFactory()
+ walkSchema, this
);
if (executeWalk) {
validationMessages.addAll(walkSchema.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema));
@@ -162,11 +171,8 @@ public class ItemsValidator202012 extends BaseJsonValidator {
node,
rootNode,
instanceLocation,
- this.evaluationPath,
- walkSchema.getSchemaLocation(),
- walkSchema.getSchemaNode(),
- walkSchema.getParentSchema(),
- this.validationContext, this.validationContext.getJsonSchemaFactory(), validationMessages
+ walkSchema,
+ this, validationMessages
);
//@formatter:on
}
diff --git a/src/main/java/com/networknt/schema/JsonSchema.java b/src/main/java/com/networknt/schema/JsonSchema.java
index 6c5f1d3..74ed724 100644
--- a/src/main/java/com/networknt/schema/JsonSchema.java
+++ b/src/main/java/com/networknt/schema/JsonSchema.java
@@ -22,11 +22,11 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
import com.networknt.schema.SpecVersion.VersionFlag;
import com.networknt.schema.serialization.JsonMapperFactory;
import com.networknt.schema.serialization.YamlMapperFactory;
+import com.networknt.schema.utils.JsonNodes;
import com.networknt.schema.utils.SetView;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
-import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@@ -211,7 +211,7 @@ public class JsonSchema extends BaseJsonValidator {
return this;
}
- ValidationContext getValidationContext() {
+ public ValidationContext getValidationContext() {
return this.validationContext;
}
@@ -282,7 +282,7 @@ public class JsonSchema extends BaseJsonValidator {
SchemaLocation schemaLocation = document.getSchemaLocation();
JsonNodePath evaluationPath = document.getEvaluationPath();
int nameCount = fragment.getNameCount();
- for (int x = 0; x < fragment.getNameCount(); x++) {
+ for (int x = 0; x < nameCount; x++) {
/*
* The sub schema is created by iterating through the parents in order to
* maintain the lexical parent schema context.
@@ -348,29 +348,9 @@ public class JsonSchema extends BaseJsonValidator {
protected JsonNode getNode(Object propertyOrIndex) {
return getNode(this.schemaNode, propertyOrIndex);
}
-
+
protected JsonNode getNode(JsonNode node, Object propertyOrIndex) {
- JsonNode value = null;
- if (propertyOrIndex instanceof Number) {
- value = node.get(((Number) propertyOrIndex).intValue());
- } else {
- // In the case of string this represents an escaped json pointer and thus does not reflect the property directly
- String unescaped = propertyOrIndex.toString();
- if (unescaped.contains("~")) {
- unescaped = unescaped.replace("~1", "/");
- unescaped = unescaped.replace("~0", "~");
- }
- if (unescaped.contains("%")) {
- try {
- unescaped = URLDecoder.decode(unescaped, StandardCharsets.UTF_8.toString());
- } catch (UnsupportedEncodingException e) {
- // Do nothing
- }
- }
-
- value = node.get(unescaped);
- }
- return value;
+ return JsonNodes.get(node, propertyOrIndex);
}
public JsonSchema findLexicalRoot() {
@@ -563,9 +543,7 @@ public class JsonSchema extends BaseJsonValidator {
try {
results = v.validate(executionContext, jsonNode, rootNode, instanceLocation);
} finally {
- if (results == null || results.isEmpty()) {
- // Do nothing if valid
- } else {
+ if (results != null && !results.isEmpty()) {
if (errors == null) {
errors = new SetView<>();
}
@@ -918,22 +896,24 @@ public class JsonSchema extends BaseJsonValidator {
* @return ValidationResult
*/
private ValidationResult validateAndCollect(ExecutionContext executionContext, JsonNode jsonNode, JsonNode rootNode, JsonNodePath instanceLocation) {
- // Get the config.
- SchemaValidatorsConfig config = this.validationContext.getConfig();
- // Get the collector context from the thread local.
- CollectorContext collectorContext = executionContext.getCollectorContext();
// Set the walkEnabled and isValidationEnabled flag in internal validator state.
setValidatorState(executionContext, false, true);
// Validate.
Set<ValidationMessage> errors = validate(executionContext, jsonNode, rootNode, instanceLocation);
+
+ // Get the config.
+ SchemaValidatorsConfig config = this.validationContext.getConfig();
+
// When walk is called in series of nested call we don't want to load the collectors every time. Leave to the API to decide when to call collectors.
if (config.doLoadCollectors()) {
+ // Get the collector context.
+ CollectorContext collectorContext = executionContext.getCollectorContext();
+
// Load all the data from collectors into the context.
collectorContext.loadCollectors();
}
// Collect errors and collector context into validation result.
- ValidationResult validationResult = new ValidationResult(errors, executionContext);
- return validationResult;
+ return new ValidationResult(errors, executionContext);
}
public ValidationResult validateAndCollect(JsonNode node) {
@@ -1012,22 +992,22 @@ public class JsonSchema extends BaseJsonValidator {
private ValidationResult walkAtNodeInternal(ExecutionContext executionContext, JsonNode node, JsonNode rootNode,
JsonNodePath instanceLocation, boolean shouldValidateSchema) {
- // Get the config.
- SchemaValidatorsConfig config = this.validationContext.getConfig();
- // Get the collector context.
- CollectorContext collectorContext = executionContext.getCollectorContext();
// Set the walkEnabled flag in internal validator state.
setValidatorState(executionContext, true, shouldValidateSchema);
// Walk through the schema.
Set<ValidationMessage> errors = walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema);
+
+ // Get the config.
+ SchemaValidatorsConfig config = this.validationContext.getConfig();
// When walk is called in series of nested call we don't want to load the collectors every time. Leave to the API to decide when to call collectors.
if (config.doLoadCollectors()) {
+ // Get the collector context.
+ CollectorContext collectorContext = executionContext.getCollectorContext();
+
// Load all the data from collectors into the context.
collectorContext.loadCollectors();
}
-
- ValidationResult validationResult = new ValidationResult(errors, executionContext);
- return validationResult;
+ return new ValidationResult(errors, executionContext);
}
@Override
@@ -1035,21 +1015,19 @@ public class JsonSchema extends BaseJsonValidator {
JsonNodePath instanceLocation, boolean shouldValidateSchema) {
Set<ValidationMessage> errors = new LinkedHashSet<>();
// Walk through all the JSONWalker's.
- for (JsonValidator v : getValidators()) {
- JsonNodePath evaluationPathWithKeyword = v.getEvaluationPath();
+ for (JsonValidator validator : getValidators()) {
+ JsonNodePath evaluationPathWithKeyword = validator.getEvaluationPath();
try {
// Call all the pre-walk listeners. If at least one of the pre walk listeners
// returns SKIP, then skip the walk.
if (this.validationContext.getConfig().getKeywordWalkListenerRunner().runPreWalkListeners(executionContext,
evaluationPathWithKeyword.getName(-1), node, rootNode, instanceLocation,
- v.getEvaluationPath(), v.getSchemaLocation(), this.schemaNode,
- this.parentSchema, this.validationContext, this.validationContext.getJsonSchemaFactory())) {
+ this, validator)) {
Set<ValidationMessage> results = null;
try {
- results = v.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema);
+ results = validator.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema);
} finally {
- if (results == null || results.isEmpty()) {
- } else {
+ if (results != null && !results.isEmpty()) {
errors.addAll(results);
}
}
@@ -1058,9 +1036,7 @@ public class JsonSchema extends BaseJsonValidator {
// Call all the post-walk listeners.
this.validationContext.getConfig().getKeywordWalkListenerRunner().runPostWalkListeners(executionContext,
evaluationPathWithKeyword.getName(-1), node, rootNode, instanceLocation,
- v.getEvaluationPath(), v.getSchemaLocation(), this.schemaNode,
- this.parentSchema, this.validationContext, this.validationContext.getJsonSchemaFactory(),
- errors);
+ this, validator, errors);
}
}
return errors;
diff --git a/src/main/java/com/networknt/schema/MaxPropertiesValidator.java b/src/main/java/com/networknt/schema/MaxPropertiesValidator.java
index f23f303..0d33b12 100644
--- a/src/main/java/com/networknt/schema/MaxPropertiesValidator.java
+++ b/src/main/java/com/networknt/schema/MaxPropertiesValidator.java
@@ -29,13 +29,15 @@ import java.util.Set;
public class MaxPropertiesValidator extends BaseJsonValidator implements JsonValidator {
private static final Logger logger = LoggerFactory.getLogger(MaxPropertiesValidator.class);
- private int max;
+ private final int max;
public MaxPropertiesValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema,
ValidationContext validationContext) {
super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.MAX_PROPERTIES, validationContext);
if (schemaNode.canConvertToExactIntegral()) {
max = schemaNode.intValue();
+ } else {
+ max = Integer.MAX_VALUE;
}
}
diff --git a/src/main/java/com/networknt/schema/MinPropertiesValidator.java b/src/main/java/com/networknt/schema/MinPropertiesValidator.java
index 26d6de1..1ff5664 100644
--- a/src/main/java/com/networknt/schema/MinPropertiesValidator.java
+++ b/src/main/java/com/networknt/schema/MinPropertiesValidator.java
@@ -29,13 +29,15 @@ import java.util.Set;
public class MinPropertiesValidator extends BaseJsonValidator implements JsonValidator {
private static final Logger logger = LoggerFactory.getLogger(MinPropertiesValidator.class);
- protected int min;
+ protected final int min;
public MinPropertiesValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema,
ValidationContext validationContext) {
super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.MIN_PROPERTIES, validationContext);
if (schemaNode.canConvertToExactIntegral()) {
min = schemaNode.intValue();
+ } else {
+ min = 0;
}
}
diff --git a/src/main/java/com/networknt/schema/PrefixItemsValidator.java b/src/main/java/com/networknt/schema/PrefixItemsValidator.java
index d798503..94bd136 100644
--- a/src/main/java/com/networknt/schema/PrefixItemsValidator.java
+++ b/src/main/java/com/networknt/schema/PrefixItemsValidator.java
@@ -19,6 +19,7 @@ package com.networknt.schema;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.networknt.schema.annotation.JsonNodeAnnotation;
+import com.networknt.schema.utils.JsonSchemaRefs;
import com.networknt.schema.utils.SetView;
import org.slf4j.Logger;
@@ -107,7 +108,7 @@ public class PrefixItemsValidator extends BaseJsonValidator {
int count = Math.min(node.size(), this.tupleSchema.size());
for (int i = 0; i < count; ++i) {
JsonNode n = node.get(i);
- JsonNode defaultNode = this.tupleSchema.get(i).getSchemaNode().get("default");
+ JsonNode defaultNode = getDefaultNode(this.tupleSchema.get(i));
if (n.isNull() && defaultNode != null) {
array.set(i, defaultNode);
n = defaultNode;
@@ -119,6 +120,17 @@ public class PrefixItemsValidator extends BaseJsonValidator {
return validationMessages;
}
+ private static JsonNode getDefaultNode(JsonSchema schema) {
+ JsonNode result = schema.getSchemaNode().get("default");
+ if (result == null) {
+ JsonSchemaRef schemaRef = JsonSchemaRefs.from(schema);
+ if (schemaRef != null) {
+ result = getDefaultNode(schemaRef.getSchema());
+ }
+ }
+ return result;
+ }
+
private void doWalk(ExecutionContext executionContext, Set<ValidationMessage> validationMessages, int i,
JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation, boolean shouldValidateSchema) {
walkSchema(executionContext, this.tupleSchema.get(i), node, rootNode, instanceLocation.append(i),
@@ -134,10 +146,7 @@ public class PrefixItemsValidator extends BaseJsonValidator {
node,
rootNode,
instanceLocation,
- walkSchema.getEvaluationPath(),
- walkSchema.getSchemaLocation(),
- walkSchema.getSchemaNode(),
- walkSchema.getParentSchema(), this.validationContext, this.validationContext.getJsonSchemaFactory()
+ walkSchema, this
);
if (executeWalk) {
validationMessages.addAll(walkSchema.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema));
@@ -148,11 +157,8 @@ public class PrefixItemsValidator extends BaseJsonValidator {
node,
rootNode,
instanceLocation,
- this.evaluationPath,
- walkSchema.getSchemaLocation(),
- walkSchema.getSchemaNode(),
- walkSchema.getParentSchema(),
- this.validationContext, this.validationContext.getJsonSchemaFactory(), validationMessages
+ walkSchema,
+ this, validationMessages
);
//@formatter:on
}
diff --git a/src/main/java/com/networknt/schema/PropertiesValidator.java b/src/main/java/com/networknt/schema/PropertiesValidator.java
index 45e055d..a47e61c 100644
--- a/src/main/java/com/networknt/schema/PropertiesValidator.java
+++ b/src/main/java/com/networknt/schema/PropertiesValidator.java
@@ -20,6 +20,7 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.JsonNodeType;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.networknt.schema.annotation.JsonNodeAnnotation;
+import com.networknt.schema.utils.JsonSchemaRefs;
import com.networknt.schema.utils.SetView;
import com.networknt.schema.walk.WalkListenerRunner;
import org.slf4j.Logger;
@@ -98,7 +99,7 @@ public class PropertiesValidator extends BaseJsonValidator {
if (errors == null) {
errors = new SetView<>();
}
- walkSchema(executionContext, entry, node, rootNode, instanceLocation, state.isValidationEnabled(), errors, this.validationContext.getConfig().getPropertyWalkListenerRunner());
+ walkSchema(executionContext, entry, node, rootNode, instanceLocation, state.isValidationEnabled(), errors, this.validationContext.getConfig().getPropertyWalkListenerRunner(), false);
}
// reset the complex flag to the original value before the recursive call
@@ -108,6 +109,18 @@ public class PropertiesValidator extends BaseJsonValidator {
state.setMatchedNode(true);
}
} else {
+ if (state.isWalkEnabled()) {
+ // This tries to make the walk listener consistent between when validation is
+ // enabled or disabled as when validation is disabled it will walk where node is
+ // null.
+ // The actual walk needs to be skipped as the validators assume that node is not
+ // null.
+ if (errors == null) {
+ errors = new SetView<>();
+ }
+ walkSchema(executionContext, entry, node, rootNode, instanceLocation, state.isValidationEnabled(), errors, this.validationContext.getConfig().getPropertyWalkListenerRunner(), true);
+ }
+
// check whether the node which has not matched was mandatory or not
// the node was mandatory, decide which behavior to employ when validator has
@@ -156,7 +169,7 @@ public class PropertiesValidator extends BaseJsonValidator {
} else {
WalkListenerRunner propertyWalkListenerRunner = this.validationContext.getConfig().getPropertyWalkListenerRunner();
for (Map.Entry<String, JsonSchema> entry : this.schemas.entrySet()) {
- walkSchema(executionContext, entry, node, rootNode, instanceLocation, shouldValidateSchema, validationMessages, propertyWalkListenerRunner);
+ walkSchema(executionContext, entry, node, rootNode, instanceLocation, shouldValidateSchema, validationMessages, propertyWalkListenerRunner, false);
}
}
return validationMessages;
@@ -177,7 +190,7 @@ public class PropertiesValidator extends BaseJsonValidator {
for (Map.Entry<String, JsonSchema> entry : this.schemas.entrySet()) {
JsonNode propertyNode = node.get(entry.getKey());
- JsonNode defaultNode = getDefaultNode(entry);
+ JsonNode defaultNode = getDefaultNode(entry.getValue());
if (defaultNode == null) {
continue;
}
@@ -189,29 +202,37 @@ public class PropertiesValidator extends BaseJsonValidator {
}
}
- private static JsonNode getDefaultNode(final Map.Entry<String, JsonSchema> entry) {
- JsonSchema propertySchema = entry.getValue();
- return propertySchema.getSchemaNode().get("default");
+ private static JsonNode getDefaultNode(JsonSchema schema) {
+ JsonNode result = schema.getSchemaNode().get("default");
+ if (result == null) {
+ JsonSchemaRef schemaRef = JsonSchemaRefs.from(schema);
+ if (schemaRef != null) {
+ result = getDefaultNode(schemaRef.getSchema());
+ }
+ }
+ return result;
}
private void walkSchema(ExecutionContext executionContext, Map.Entry<String, JsonSchema> entry, JsonNode node,
JsonNode rootNode, JsonNodePath instanceLocation, boolean shouldValidateSchema,
- SetView<ValidationMessage> validationMessages, WalkListenerRunner propertyWalkListenerRunner) {
+ SetView<ValidationMessage> validationMessages, WalkListenerRunner propertyWalkListenerRunner, boolean skipWalk) {
JsonSchema propertySchema = entry.getValue();
JsonNode propertyNode = (node == null ? null : node.get(entry.getKey()));
JsonNodePath path = instanceLocation.append(entry.getKey());
boolean executeWalk = propertyWalkListenerRunner.runPreWalkListeners(executionContext,
ValidatorTypeCode.PROPERTIES.getValue(), propertyNode, rootNode, path,
- propertySchema.getEvaluationPath(), propertySchema.getSchemaLocation(), propertySchema.getSchemaNode(),
- propertySchema.getParentSchema(), this.validationContext, this.validationContext.getJsonSchemaFactory());
- if (executeWalk) {
+ propertySchema, this);
+ if (propertyNode == null && node != null) {
+ // Attempt to get the property node again in case the propertyNode was updated
+ propertyNode = node.get(entry.getKey());
+ }
+ if (executeWalk && !(skipWalk && propertyNode == null)) {
validationMessages.union(
propertySchema.walk(executionContext, propertyNode, rootNode, path, shouldValidateSchema));
}
propertyWalkListenerRunner.runPostWalkListeners(executionContext, ValidatorTypeCode.PROPERTIES.getValue(), propertyNode,
- rootNode, path, propertySchema.getEvaluationPath(),
- propertySchema.getSchemaLocation(), propertySchema.getSchemaNode(), propertySchema.getParentSchema(), this.validationContext, this.validationContext.getJsonSchemaFactory(), validationMessages);
-
+ rootNode, path, propertySchema,
+ this, validationMessages);
}
public Map<String, JsonSchema> getSchemas() {
diff --git a/src/main/java/com/networknt/schema/RecursiveRefValidator.java b/src/main/java/com/networknt/schema/RecursiveRefValidator.java
index 0ca0f26..e73bb7d 100644
--- a/src/main/java/com/networknt/schema/RecursiveRefValidator.java
+++ b/src/main/java/com/networknt/schema/RecursiveRefValidator.java
@@ -28,7 +28,7 @@ import java.util.*;
public class RecursiveRefValidator extends BaseJsonValidator {
private static final Logger logger = LoggerFactory.getLogger(RecursiveRefValidator.class);
- protected JsonSchemaRef schema;
+ protected final JsonSchemaRef schema;
public RecursiveRefValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.RECURSIVE_REF, validationContext);
diff --git a/src/main/java/com/networknt/schema/RefValidator.java b/src/main/java/com/networknt/schema/RefValidator.java
index 1959231..1a32e6e 100644
--- a/src/main/java/com/networknt/schema/RefValidator.java
+++ b/src/main/java/com/networknt/schema/RefValidator.java
@@ -28,7 +28,7 @@ import java.util.*;
public class RefValidator extends BaseJsonValidator {
private static final Logger logger = LoggerFactory.getLogger(RefValidator.class);
- protected JsonSchemaRef schema;
+ protected final JsonSchemaRef schema;
private static final String REF_CURRENT = "#";
diff --git a/src/main/java/com/networknt/schema/TypeValidator.java b/src/main/java/com/networknt/schema/TypeValidator.java
index 7f7736e..245f690 100644
--- a/src/main/java/com/networknt/schema/TypeValidator.java
+++ b/src/main/java/com/networknt/schema/TypeValidator.java
@@ -29,16 +29,16 @@ import java.util.*;
public class TypeValidator extends BaseJsonValidator {
private static final Logger logger = LoggerFactory.getLogger(TypeValidator.class);
- private JsonType schemaType;
- private JsonSchema parentSchema;
- private UnionTypeValidator unionTypeValidator;
+ private final JsonType schemaType;
+ private final UnionTypeValidator unionTypeValidator;
public TypeValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.TYPE, validationContext);
this.schemaType = TypeFactory.getSchemaNodeType(schemaNode);
- this.parentSchema = parentSchema;
if (this.schemaType == JsonType.UNION) {
this.unionTypeValidator = new UnionTypeValidator(schemaLocation, evaluationPath, schemaNode, parentSchema, validationContext);
+ } else {
+ this.unionTypeValidator = null;
}
}
@@ -47,7 +47,7 @@ public class TypeValidator extends BaseJsonValidator {
}
public boolean equalsToSchemaType(JsonNode node) {
- return JsonNodeUtil.equalsToSchemaType(node,this.schemaType, this.parentSchema, this.validationContext);
+ return JsonNodeUtil.equalsToSchemaType(node, this.schemaType, this.parentSchema, this.validationContext);
}
@Override
diff --git a/src/main/java/com/networknt/schema/UniqueItemsValidator.java b/src/main/java/com/networknt/schema/UniqueItemsValidator.java
index be43b96..a061b97 100644
--- a/src/main/java/com/networknt/schema/UniqueItemsValidator.java
+++ b/src/main/java/com/networknt/schema/UniqueItemsValidator.java
@@ -30,12 +30,14 @@ import java.util.Set;
public class UniqueItemsValidator extends BaseJsonValidator implements JsonValidator {
private static final Logger logger = LoggerFactory.getLogger(UniqueItemsValidator.class);
- private boolean unique = false;
+ private final boolean unique;
public UniqueItemsValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.UNIQUE_ITEMS, validationContext);
if (schemaNode.isBoolean()) {
unique = schemaNode.booleanValue();
+ } else {
+ unique = false;
}
}
diff --git a/src/main/java/com/networknt/schema/utils/JsonNodes.java b/src/main/java/com/networknt/schema/utils/JsonNodes.java
new file mode 100644
index 0000000..5653959
--- /dev/null
+++ b/src/main/java/com/networknt/schema/utils/JsonNodes.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2024 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.utils;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.networknt.schema.JsonNodePath;
+
+/**
+ * Utility methods for JsonNode.
+ */
+public class JsonNodes {
+ /**
+ * Gets the node found at the path.
+ *
+ * @param node the node
+ * @param path the path
+ * @return the node found at the path or null
+ */
+ @SuppressWarnings("unchecked")
+ public static <T extends JsonNode> T get(JsonNode node, JsonNodePath path) {
+ int nameCount = path.getNameCount();
+ JsonNode current = node;
+ for (int x = 0; x < nameCount; x++) {
+ Object segment = path.getElement(x);
+ JsonNode result = get(current, segment);
+ if (result == null) {
+ return null;
+ }
+ current = result;
+ }
+ return (T) current;
+ }
+
+ /**
+ * Gets the node given the property or index.
+ *
+ * @param node the node
+ * @param propertyOrIndex the property or index
+ * @return the node given the property or index
+ */
+ @SuppressWarnings("unchecked")
+ public static <T extends JsonNode> T get(JsonNode node, Object propertyOrIndex) {
+ JsonNode value = null;
+ if (propertyOrIndex instanceof Number) {
+ value = node.get(((Number) propertyOrIndex).intValue());
+ } else {
+ // In the case of string this represents an escaped json pointer and thus does not reflect the property directly
+ String unescaped = propertyOrIndex.toString();
+ if (unescaped.contains("~")) {
+ unescaped = unescaped.replace("~1", "/");
+ unescaped = unescaped.replace("~0", "~");
+ }
+ if (unescaped.contains("%")) {
+ try {
+ unescaped = URLDecoder.decode(unescaped, StandardCharsets.UTF_8.toString());
+ } catch (UnsupportedEncodingException e) {
+ // Do nothing
+ }
+ }
+
+ value = node.get(unescaped);
+ }
+ return (T) value;
+ }
+}
diff --git a/src/main/java/com/networknt/schema/utils/JsonSchemaRefs.java b/src/main/java/com/networknt/schema/utils/JsonSchemaRefs.java
new file mode 100644
index 0000000..9a6231f
--- /dev/null
+++ b/src/main/java/com/networknt/schema/utils/JsonSchemaRefs.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2024 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.utils;
+
+import com.networknt.schema.DynamicRefValidator;
+import com.networknt.schema.JsonSchema;
+import com.networknt.schema.JsonSchemaRef;
+import com.networknt.schema.JsonValidator;
+import com.networknt.schema.RecursiveRefValidator;
+import com.networknt.schema.RefValidator;
+
+/**
+ * Utility methods for JsonSchemaRef.
+ */
+public class JsonSchemaRefs {
+
+ /**
+ * Gets the ref.
+ *
+ * @param schema the schema
+ * @return the ref
+ */
+ public static JsonSchemaRef from(JsonSchema schema) {
+ for (JsonValidator validator : schema.getValidators()) {
+ if (validator instanceof RefValidator) {
+ return ((RefValidator) validator).getSchemaRef();
+ } else if (validator instanceof DynamicRefValidator) {
+ return ((DynamicRefValidator) validator).getSchemaRef();
+ } else if (validator instanceof RecursiveRefValidator) {
+ return ((RecursiveRefValidator) validator).getSchemaRef();
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/src/main/java/com/networknt/schema/walk/AbstractWalkListenerRunner.java b/src/main/java/com/networknt/schema/walk/AbstractWalkListenerRunner.java
index 57ea0f2..92d564c 100644
--- a/src/main/java/com/networknt/schema/walk/AbstractWalkListenerRunner.java
+++ b/src/main/java/com/networknt/schema/walk/AbstractWalkListenerRunner.java
@@ -4,9 +4,7 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.networknt.schema.ExecutionContext;
import com.networknt.schema.JsonNodePath;
import com.networknt.schema.JsonSchema;
-import com.networknt.schema.JsonSchemaFactory;
-import com.networknt.schema.SchemaLocation;
-import com.networknt.schema.ValidationContext;
+import com.networknt.schema.JsonValidator;
import com.networknt.schema.ValidationMessage;
import java.util.List;
@@ -14,14 +12,11 @@ import java.util.Set;
public abstract class AbstractWalkListenerRunner implements WalkListenerRunner {
- protected WalkEvent constructWalkEvent(ExecutionContext executionContext, String keyword, JsonNode node,
- JsonNode rootNode, JsonNodePath instanceLocation, JsonNodePath evaluationPath, SchemaLocation schemaLocation,
- JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext,
- JsonSchemaFactory currentJsonSchemaFactory) {
+ protected WalkEvent constructWalkEvent(ExecutionContext executionContext, String keyword, JsonNode instanceNode,
+ JsonNode rootNode, JsonNodePath instanceLocation, JsonSchema schema, JsonValidator validator) {
return WalkEvent.builder().executionContext(executionContext).instanceLocation(instanceLocation)
- .evaluationPath(evaluationPath).keyword(keyword).node(node).parentSchema(parentSchema)
- .rootNode(rootNode).schemaNode(schemaNode).schemaLocation(schemaLocation)
- .currentJsonSchemaFactory(currentJsonSchemaFactory).validationContext(validationContext).build();
+ .keyword(keyword).instanceNode(instanceNode)
+ .rootNode(rootNode).schema(schema).validator(validator).build();
}
protected boolean runPreWalkListeners(List<JsonSchemaWalkListener> walkListeners, WalkEvent walkEvent) {
diff --git a/src/main/java/com/networknt/schema/walk/DefaultItemWalkListenerRunner.java b/src/main/java/com/networknt/schema/walk/DefaultItemWalkListenerRunner.java
index 429771c..eba7f05 100644
--- a/src/main/java/com/networknt/schema/walk/DefaultItemWalkListenerRunner.java
+++ b/src/main/java/com/networknt/schema/walk/DefaultItemWalkListenerRunner.java
@@ -4,9 +4,7 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.networknt.schema.ExecutionContext;
import com.networknt.schema.JsonNodePath;
import com.networknt.schema.JsonSchema;
-import com.networknt.schema.JsonSchemaFactory;
-import com.networknt.schema.SchemaLocation;
-import com.networknt.schema.ValidationContext;
+import com.networknt.schema.JsonValidator;
import com.networknt.schema.ValidationMessage;
import java.util.List;
@@ -21,22 +19,18 @@ public class DefaultItemWalkListenerRunner extends AbstractWalkListenerRunner {
}
@Override
- public boolean runPreWalkListeners(ExecutionContext executionContext, String keyword, JsonNode node,
- JsonNode rootNode, JsonNodePath instanceLocation, JsonNodePath evaluationPath, SchemaLocation schemaLocation,
- JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext,
- JsonSchemaFactory currentJsonSchemaFactory) {
- WalkEvent walkEvent = constructWalkEvent(executionContext, keyword, node, rootNode, instanceLocation,
- evaluationPath, schemaLocation, schemaNode, parentSchema, validationContext, currentJsonSchemaFactory);
+ public boolean runPreWalkListeners(ExecutionContext executionContext, String keyword, JsonNode instanceNode,
+ JsonNode rootNode, JsonNodePath instanceLocation, JsonSchema schema, JsonValidator validator) {
+ WalkEvent walkEvent = constructWalkEvent(executionContext, keyword, instanceNode, rootNode, instanceLocation,
+ schema, validator);
return runPreWalkListeners(itemWalkListeners, walkEvent);
}
@Override
- public void runPostWalkListeners(ExecutionContext executionContext, String keyword, JsonNode node,
- JsonNode rootNode, JsonNodePath instanceLocation, JsonNodePath evaluationPath, SchemaLocation schemaLocation,
- JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext,
- JsonSchemaFactory currentJsonSchemaFactory, Set<ValidationMessage> validationMessages) {
- WalkEvent walkEvent = constructWalkEvent(executionContext, keyword, node, rootNode, instanceLocation,
- evaluationPath, schemaLocation, schemaNode, parentSchema, validationContext, currentJsonSchemaFactory);
+ public void runPostWalkListeners(ExecutionContext executionContext, String keyword, JsonNode instanceNode,
+ JsonNode rootNode, JsonNodePath instanceLocation, JsonSchema schema, JsonValidator validator, Set<ValidationMessage> validationMessages) {
+ WalkEvent walkEvent = constructWalkEvent(executionContext, keyword, instanceNode, rootNode, instanceLocation,
+ schema, validator);
runPostWalkListeners(itemWalkListeners, walkEvent, validationMessages);
}
diff --git a/src/main/java/com/networknt/schema/walk/DefaultKeywordWalkListenerRunner.java b/src/main/java/com/networknt/schema/walk/DefaultKeywordWalkListenerRunner.java
index 04b1106..0435f53 100644
--- a/src/main/java/com/networknt/schema/walk/DefaultKeywordWalkListenerRunner.java
+++ b/src/main/java/com/networknt/schema/walk/DefaultKeywordWalkListenerRunner.java
@@ -16,13 +16,10 @@ public class DefaultKeywordWalkListenerRunner extends AbstractWalkListenerRunner
}
@Override
- public boolean runPreWalkListeners(ExecutionContext executionContext, String keyword, JsonNode node, JsonNode rootNode,
- JsonNodePath instanceLocation, JsonNodePath evaluationPath, SchemaLocation schemaLocation,
- JsonNode schemaNode,
- JsonSchema parentSchema, ValidationContext validationContext, JsonSchemaFactory currentJsonSchemaFactory) {
+ public boolean runPreWalkListeners(ExecutionContext executionContext, String keyword, JsonNode instanceNode, JsonNode rootNode,
+ JsonNodePath instanceLocation, JsonSchema schema, JsonValidator validator) {
boolean continueRunningListenersAndWalk = true;
- WalkEvent keywordWalkEvent = constructWalkEvent(executionContext, keyword, node, rootNode, instanceLocation, evaluationPath,
- schemaLocation, schemaNode, parentSchema, validationContext, currentJsonSchemaFactory);
+ WalkEvent keywordWalkEvent = constructWalkEvent(executionContext, keyword, instanceNode, rootNode, instanceLocation, schema, validator);
// Run Listeners that are setup only for this keyword.
List<JsonSchemaWalkListener> currentKeywordListeners = keywordWalkListenersMap.get(keyword);
continueRunningListenersAndWalk = runPreWalkListeners(currentKeywordListeners, keywordWalkEvent);
@@ -36,13 +33,9 @@ public class DefaultKeywordWalkListenerRunner extends AbstractWalkListenerRunner
}
@Override
- public void runPostWalkListeners(ExecutionContext executionContext, String keyword, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation,
- JsonNodePath evaluationPath, SchemaLocation schemaLocation,
- JsonNode schemaNode,
- JsonSchema parentSchema,
- ValidationContext validationContext, JsonSchemaFactory currentJsonSchemaFactory, Set<ValidationMessage> validationMessages) {
- WalkEvent keywordWalkEvent = constructWalkEvent(executionContext, keyword, node, rootNode, instanceLocation, evaluationPath,
- schemaLocation, schemaNode, parentSchema, validationContext, currentJsonSchemaFactory);
+ public void runPostWalkListeners(ExecutionContext executionContext, String keyword, JsonNode instanceNode, JsonNode rootNode, JsonNodePath instanceLocation,
+ JsonSchema schema, JsonValidator validator, Set<ValidationMessage> validationMessages) {
+ WalkEvent keywordWalkEvent = constructWalkEvent(executionContext, keyword, instanceNode, rootNode, instanceLocation, schema, validator);
// Run Listeners that are setup only for this keyword.
List<JsonSchemaWalkListener> currentKeywordListeners = keywordWalkListenersMap.get(keyword);
runPostWalkListeners(currentKeywordListeners, keywordWalkEvent, validationMessages);
diff --git a/src/main/java/com/networknt/schema/walk/DefaultPropertyWalkListenerRunner.java b/src/main/java/com/networknt/schema/walk/DefaultPropertyWalkListenerRunner.java
index a8e937d..e10253f 100644
--- a/src/main/java/com/networknt/schema/walk/DefaultPropertyWalkListenerRunner.java
+++ b/src/main/java/com/networknt/schema/walk/DefaultPropertyWalkListenerRunner.java
@@ -4,9 +4,7 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.networknt.schema.ExecutionContext;
import com.networknt.schema.JsonNodePath;
import com.networknt.schema.JsonSchema;
-import com.networknt.schema.JsonSchemaFactory;
-import com.networknt.schema.SchemaLocation;
-import com.networknt.schema.ValidationContext;
+import com.networknt.schema.JsonValidator;
import com.networknt.schema.ValidationMessage;
import java.util.List;
@@ -21,20 +19,16 @@ public class DefaultPropertyWalkListenerRunner extends AbstractWalkListenerRunne
}
@Override
- public boolean runPreWalkListeners(ExecutionContext executionContext, String keyword, JsonNode node, JsonNode rootNode,
- JsonNodePath instanceLocation, JsonNodePath evaluationPath, SchemaLocation schemaLocation, JsonNode schemaNode,
- JsonSchema parentSchema, ValidationContext validationContext, JsonSchemaFactory currentJsonSchemaFactory) {
- WalkEvent walkEvent = constructWalkEvent(executionContext, keyword, node, rootNode, instanceLocation, evaluationPath, schemaLocation,
- schemaNode, parentSchema, validationContext, currentJsonSchemaFactory);
+ public boolean runPreWalkListeners(ExecutionContext executionContext, String keyword, JsonNode instanceNode, JsonNode rootNode,
+ JsonNodePath instanceLocation, JsonSchema schema, JsonValidator validator) {
+ WalkEvent walkEvent = constructWalkEvent(executionContext, keyword, instanceNode, rootNode, instanceLocation, schema, validator);
return runPreWalkListeners(propertyWalkListeners, walkEvent);
}
@Override
- public void runPostWalkListeners(ExecutionContext executionContext, String keyword, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation,
- JsonNodePath evaluationPath, SchemaLocation schemaLocation, JsonNode schemaNode, JsonSchema parentSchema,
- ValidationContext validationContext, JsonSchemaFactory currentJsonSchemaFactory, Set<ValidationMessage> validationMessages) {
- WalkEvent walkEvent = constructWalkEvent(executionContext, keyword, node, rootNode, instanceLocation, evaluationPath, schemaLocation,
- schemaNode, parentSchema, validationContext, currentJsonSchemaFactory);
+ public void runPostWalkListeners(ExecutionContext executionContext, String keyword, JsonNode instanceNode, JsonNode rootNode, JsonNodePath instanceLocation,
+ JsonSchema schema, JsonValidator validator, Set<ValidationMessage> validationMessages) {
+ WalkEvent walkEvent = constructWalkEvent(executionContext, keyword, instanceNode, rootNode, instanceLocation, schema, validator);
runPostWalkListeners(propertyWalkListeners, walkEvent, validationMessages);
}
diff --git a/src/main/java/com/networknt/schema/walk/WalkEvent.java b/src/main/java/com/networknt/schema/walk/WalkEvent.java
index bf7cf6c..f0a2079 100644
--- a/src/main/java/com/networknt/schema/walk/WalkEvent.java
+++ b/src/main/java/com/networknt/schema/walk/WalkEvent.java
@@ -4,10 +4,7 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.networknt.schema.ExecutionContext;
import com.networknt.schema.JsonNodePath;
import com.networknt.schema.JsonSchema;
-import com.networknt.schema.JsonSchemaFactory;
-import com.networknt.schema.SchemaLocation;
-import com.networknt.schema.SchemaValidatorsConfig;
-import com.networknt.schema.ValidationContext;
+import com.networknt.schema.JsonValidator;
/**
* Encapsulation of Walk data that is passed into the {@link JsonSchemaWalkListener}.
@@ -15,73 +12,89 @@ import com.networknt.schema.ValidationContext;
public class WalkEvent {
private ExecutionContext executionContext;
- private SchemaLocation schemaLocation;
- private JsonNodePath evaluationPath;
- private JsonNode schemaNode;
- private JsonSchema parentSchema;
+ private JsonSchema schema;
private String keyword;
- private JsonNode node;
private JsonNode rootNode;
+ private JsonNode instanceNode;
private JsonNodePath instanceLocation;
- private JsonSchemaFactory currentJsonSchemaFactory;
- private ValidationContext validationContext;
-
+ private JsonValidator validator;
+
+ /**
+ * Gets the execution context.
+ * <p>
+ * As the listeners should be state-less, this allows listeners to store data in
+ * the collector context.
+ *
+ * @return the execution context
+ */
public ExecutionContext getExecutionContext() {
return executionContext;
}
- public SchemaLocation getSchemaLocation() {
- return schemaLocation;
- }
-
- public JsonNodePath getEvaluationPath() {
- return evaluationPath;
- }
-
- public JsonNode getSchemaNode() {
- return schemaNode;
- }
-
- public JsonSchema getParentSchema() {
- return parentSchema;
+ /**
+ * Gets the schema that will be used to evaluate the instance node.
+ * <p>
+ * For the keyword listener, this will allow getting the validator for the given keyword.
+ *
+ * @return the schema
+ */
+ public JsonSchema getSchema() {
+ return schema;
}
+ /**
+ * Gets the keyword.
+ *
+ * @return the keyword
+ */
public String getKeyword() {
return keyword;
}
- public JsonNode getNode() {
- return node;
- }
-
+ /**
+ * Gets the root instance node.
+ * <p>
+ * This makes it possible to get the parent node, for instance by getting the
+ * instance location parent and using the root node.
+ *
+ * @return the root node
+ */
public JsonNode getRootNode() {
return rootNode;
}
- public JsonNodePath getInstanceLocation() {
- return instanceLocation;
+ /**
+ * Gets the instance node.
+ *
+ * @return the instance node
+ */
+ public JsonNode getInstanceNode() {
+ return instanceNode;
}
- public JsonSchema getRefSchema(SchemaLocation schemaUri) {
- return currentJsonSchemaFactory.getSchema(schemaUri, validationContext.getConfig());
- }
-
- public JsonSchema getRefSchema(SchemaLocation schemaUri, SchemaValidatorsConfig schemaValidatorsConfig) {
- if (schemaValidatorsConfig != null) {
- return currentJsonSchemaFactory.getSchema(schemaUri, schemaValidatorsConfig);
- } else {
- return getRefSchema(schemaUri);
- }
+ /**
+ * Gets the instance location of the instance node.
+ *
+ * @return the instance location of the instance node
+ */
+ public JsonNodePath getInstanceLocation() {
+ return instanceLocation;
}
- public JsonSchemaFactory getCurrentJsonSchemaFactory() {
- return currentJsonSchemaFactory;
+ /**
+ * Gets the validator that corresponds with the keyword.
+ *
+ * @return the validator
+ */
+ @SuppressWarnings("unchecked")
+ public <T extends JsonValidator> T getValidator() {
+ return (T) this.validator;
}
@Override
public String toString() {
- return "WalkEvent [evaluationPath=" + evaluationPath + ", schemaLocation=" + schemaLocation
- + ", instanceLocation=" + instanceLocation + "]";
+ return "WalkEvent [evaluationPath=" + getSchema().getEvaluationPath() + ", schemaLocation="
+ + getSchema().getSchemaLocation() + ", instanceLocation=" + instanceLocation + "]";
}
static class WalkEventBuilder {
@@ -97,23 +110,8 @@ public class WalkEvent {
return this;
}
- public WalkEventBuilder evaluationPath(JsonNodePath evaluationPath) {
- walkEvent.evaluationPath = evaluationPath;
- return this;
- }
-
- public WalkEventBuilder schemaLocation(SchemaLocation schemaLocation) {
- walkEvent.schemaLocation = schemaLocation;
- return this;
- }
-
- public WalkEventBuilder schemaNode(JsonNode schemaNode) {
- walkEvent.schemaNode = schemaNode;
- return this;
- }
-
- public WalkEventBuilder parentSchema(JsonSchema parentSchema) {
- walkEvent.parentSchema = parentSchema;
+ public WalkEventBuilder schema(JsonSchema schema) {
+ walkEvent.schema = schema;
return this;
}
@@ -122,8 +120,8 @@ public class WalkEvent {
return this;
}
- public WalkEventBuilder node(JsonNode node) {
- walkEvent.node = node;
+ public WalkEventBuilder instanceNode(JsonNode node) {
+ walkEvent.instanceNode = node;
return this;
}
@@ -137,13 +135,8 @@ public class WalkEvent {
return this;
}
- public WalkEventBuilder currentJsonSchemaFactory(JsonSchemaFactory currentJsonSchemaFactory) {
- walkEvent.currentJsonSchemaFactory = currentJsonSchemaFactory;
- return this;
- }
-
- public WalkEventBuilder validationContext(ValidationContext validationContext) {
- walkEvent.validationContext = validationContext;
+ public WalkEventBuilder validator(JsonValidator validator) {
+ walkEvent.validator = validator;
return this;
}
@@ -156,5 +149,4 @@ public class WalkEvent {
public static WalkEventBuilder builder() {
return new WalkEventBuilder();
}
-
}
diff --git a/src/main/java/com/networknt/schema/walk/WalkListenerRunner.java b/src/main/java/com/networknt/schema/walk/WalkListenerRunner.java
index 4bba2c6..0476d8b 100644
--- a/src/main/java/com/networknt/schema/walk/WalkListenerRunner.java
+++ b/src/main/java/com/networknt/schema/walk/WalkListenerRunner.java
@@ -4,23 +4,17 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.networknt.schema.ExecutionContext;
import com.networknt.schema.JsonNodePath;
import com.networknt.schema.JsonSchema;
-import com.networknt.schema.JsonSchemaFactory;
-import com.networknt.schema.SchemaLocation;
-import com.networknt.schema.ValidationContext;
+import com.networknt.schema.JsonValidator;
import com.networknt.schema.ValidationMessage;
import java.util.Set;
public interface WalkListenerRunner {
- public boolean runPreWalkListeners(ExecutionContext executionContext, String keyword, JsonNode node,
- JsonNode rootNode, JsonNodePath instanceLocation, JsonNodePath evaluationPath, SchemaLocation schemaLocation,
- JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext,
- JsonSchemaFactory jsonSchemaFactory);
+ public boolean runPreWalkListeners(ExecutionContext executionContext, String keyword, JsonNode instanceNode,
+ JsonNode rootNode, JsonNodePath instanceLocation, JsonSchema schema, JsonValidator validator);
- public void runPostWalkListeners(ExecutionContext executionContext, String keyword, JsonNode node,
- JsonNode rootNode, JsonNodePath instanceLocation, JsonNodePath evaluationPath, SchemaLocation schemaLocation,
- JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext,
- JsonSchemaFactory jsonSchemaFactory, Set<ValidationMessage> validationMessages);
+ public void runPostWalkListeners(ExecutionContext executionContext, String keyword, JsonNode instanceNode,
+ JsonNode rootNode, JsonNodePath instanceLocation, JsonSchema schema, JsonValidator validator, Set<ValidationMessage> validationMessages);
}
diff --git a/src/test/java/com/networknt/schema/Issue451Test.java b/src/test/java/com/networknt/schema/Issue451Test.java
index e01e430..53a829e 100644
--- a/src/test/java/com/networknt/schema/Issue451Test.java
+++ b/src/test/java/com/networknt/schema/Issue451Test.java
@@ -70,7 +70,7 @@ public class Issue451Test {
private static class CountingWalker implements JsonSchemaWalkListener {
@Override
public WalkFlow onWalkStart(WalkEvent walkEvent) {
- SchemaLocation path = walkEvent.getSchemaLocation();
+ SchemaLocation path = walkEvent.getSchema().getSchemaLocation();
collector(walkEvent.getExecutionContext()).compute(path.toString(), (k, v) -> v == null ? 1 : v + 1);
return WalkFlow.CONTINUE;
}
diff --git a/src/test/java/com/networknt/schema/Issue467Test.java b/src/test/java/com/networknt/schema/Issue467Test.java
index 45a52db..ad8bb14 100644
--- a/src/test/java/com/networknt/schema/Issue467Test.java
+++ b/src/test/java/com/networknt/schema/Issue467Test.java
@@ -48,7 +48,7 @@ public class Issue467Test {
config.addKeywordWalkListener(ValidatorTypeCode.PROPERTIES.getValue(), new JsonSchemaWalkListener() {
@Override
public WalkFlow onWalkStart(WalkEvent walkEvent) {
- properties.add(walkEvent.getEvaluationPath());
+ properties.add(walkEvent.getSchema().getEvaluationPath().append(walkEvent.getKeyword()));
return WalkFlow.CONTINUE;
}
@@ -72,7 +72,7 @@ public class Issue467Test {
config.addPropertyWalkListener(new JsonSchemaWalkListener() {
@Override
public WalkFlow onWalkStart(WalkEvent walkEvent) {
- properties.add(walkEvent.getEvaluationPath());
+ properties.add(walkEvent.getSchema().getEvaluationPath());
return WalkFlow.CONTINUE;
}
@@ -84,7 +84,7 @@ public class Issue467Test {
JsonNode data = mapper.readTree(Issue467Test.class.getResource("/data/issue467.json"));
ValidationResult result = schema.walk(data, true);
assertEquals(
- new HashSet<>(Arrays.asList("$.properties.tags", "$.properties.tags.items[0].properties.category")),
+ new HashSet<>(Arrays.asList("$.properties.tags", "$.properties.tags.items[0].properties.category", "$.properties.tags.items[0].properties.value")),
properties.stream().map(Object::toString).collect(Collectors.toSet()));
assertEquals(1, result.getValidationMessages().size());
}
diff --git a/src/test/java/com/networknt/schema/Issue724Test.java b/src/test/java/com/networknt/schema/Issue724Test.java
index 03f0e3a..47b060c 100644
--- a/src/test/java/com/networknt/schema/Issue724Test.java
+++ b/src/test/java/com/networknt/schema/Issue724Test.java
@@ -65,14 +65,14 @@ class Issue724Test {
@Override
public WalkFlow onWalkStart(WalkEvent walkEvent) {
boolean isString =
- Optional.of(walkEvent.getSchemaNode())
+ Optional.of(walkEvent.getSchema().getSchemaNode())
.map(jsonNode -> jsonNode.get("type"))
.map(JsonNode::asText)
.map(type -> type.equals("string"))
.orElse(false);
if (isString) {
- this.strings.add(walkEvent.getNode().asText());
+ this.strings.add(walkEvent.getInstanceNode().asText());
}
return WalkFlow.CONTINUE;
diff --git a/src/test/java/com/networknt/schema/JsonWalkTest.java b/src/test/java/com/networknt/schema/JsonWalkTest.java
index 492bb5a..3ff08e1 100644
--- a/src/test/java/com/networknt/schema/JsonWalkTest.java
+++ b/src/test/java/com/networknt/schema/JsonWalkTest.java
@@ -162,15 +162,15 @@ public class JsonWalkTest {
public WalkFlow onWalkStart(WalkEvent keywordWalkEvent) {
ObjectMapper mapper = new ObjectMapper();
String keyWordName = keywordWalkEvent.getKeyword();
- JsonNode schemaNode = keywordWalkEvent.getSchemaNode();
+ JsonNode schemaNode = keywordWalkEvent.getSchema().getSchemaNode();
CollectorContext collectorContext = keywordWalkEvent.getExecutionContext().getCollectorContext();
if (collectorContext.get(SAMPLE_WALK_COLLECTOR_TYPE) == null) {
collectorContext.add(SAMPLE_WALK_COLLECTOR_TYPE, mapper.createObjectNode());
}
if (keyWordName.equals(CUSTOM_KEYWORD) && schemaNode.get(CUSTOM_KEYWORD).isArray()) {
ObjectNode objectNode = (ObjectNode) collectorContext.get(SAMPLE_WALK_COLLECTOR_TYPE);
- objectNode.put(keywordWalkEvent.getSchemaNode().get("title").textValue().toUpperCase(),
- keywordWalkEvent.getNode().textValue());
+ objectNode.put(keywordWalkEvent.getSchema().getSchemaNode().get("title").textValue().toUpperCase(),
+ keywordWalkEvent.getInstanceNode().textValue());
}
return WalkFlow.CONTINUE;
}
@@ -191,8 +191,8 @@ public class JsonWalkTest {
collectorContext.add(SAMPLE_WALK_COLLECTOR_TYPE, mapper.createObjectNode());
}
ObjectNode objectNode = (ObjectNode) collectorContext.get(SAMPLE_WALK_COLLECTOR_TYPE);
- objectNode.set(keywordWalkEvent.getSchemaNode().get("title").textValue().toLowerCase(),
- keywordWalkEvent.getNode());
+ objectNode.set(keywordWalkEvent.getSchema().getSchemaNode().get("title").textValue().toLowerCase(),
+ keywordWalkEvent.getInstanceNode());
return WalkFlow.SKIP;
}
@@ -206,7 +206,7 @@ public class JsonWalkTest {
@Override
public WalkFlow onWalkStart(WalkEvent keywordWalkEvent) {
- JsonNode schemaNode = keywordWalkEvent.getSchemaNode();
+ JsonNode schemaNode = keywordWalkEvent.getSchema().getSchemaNode();
if (schemaNode.get("title").textValue().equals("Property3")) {
return WalkFlow.SKIP;
}
diff --git a/src/test/java/com/networknt/schema/walk/JsonSchemaWalkListenerTest.java b/src/test/java/com/networknt/schema/walk/JsonSchemaWalkListenerTest.java
new file mode 100644
index 0000000..d4b6701
--- /dev/null
+++ b/src/test/java/com/networknt/schema/walk/JsonSchemaWalkListenerTest.java
@@ -0,0 +1,814 @@
+/*
+ * Copyright (c) 2024 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.walk;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Supplier;
+
+import org.junit.jupiter.api.Test;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.networknt.schema.ApplyDefaultsStrategy;
+import com.networknt.schema.InputFormat;
+import com.networknt.schema.ItemsValidator;
+import com.networknt.schema.ItemsValidator202012;
+import com.networknt.schema.JsonSchema;
+import com.networknt.schema.JsonSchemaFactory;
+import com.networknt.schema.JsonSchemaRef;
+import com.networknt.schema.PathType;
+import com.networknt.schema.PropertiesValidator;
+import com.networknt.schema.SchemaId;
+import com.networknt.schema.SchemaLocation;
+import com.networknt.schema.SchemaValidatorsConfig;
+import com.networknt.schema.SpecVersion.VersionFlag;
+import com.networknt.schema.serialization.JsonMapperFactory;
+import com.networknt.schema.utils.JsonNodes;
+import com.networknt.schema.utils.JsonSchemaRefs;
+import com.networknt.schema.ValidationMessage;
+import com.networknt.schema.ValidationResult;
+import com.networknt.schema.ValidatorTypeCode;
+
+/**
+ * JsonSchemaWalkListenerTest.
+ */
+class JsonSchemaWalkListenerTest {
+
+ @Test
+ void keywordListener() {
+ String schemaData = "{\r\n"
+ + " \"$schema\": \"http://json-schema.org/draft-07/schema#\",\r\n"
+ + " \"type\": \"object\",\r\n"
+ + " \"description\": \"Default Description\",\r\n"
+ + " \"properties\": {\r\n"
+ + " \"tags\": {\r\n"
+ + " \"type\": \"array\",\r\n"
+ + " \"items\": {\r\n"
+ + " \"$ref\": \"#/definitions/tag\"\r\n"
+ + " }\r\n"
+ + " }\r\n"
+ + " },\r\n"
+ + " \"definitions\": {\r\n"
+ + " \"tag\": {\r\n"
+ + " \"properties\": {\r\n"
+ + " \"name\": {\r\n"
+ + " \"type\": \"string\"\r\n"
+ + " },\r\n"
+ + " \"description\": {\r\n"
+ + " \"type\": \"string\"\r\n"
+ + " }\r\n"
+ + " }\r\n"
+ + " }\r\n"
+ + " }\r\n"
+ + "}";
+
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setPathType(PathType.JSON_POINTER);
+ config.addKeywordWalkListener(ValidatorTypeCode.PROPERTIES.getValue(), new JsonSchemaWalkListener() {
+
+ @Override
+ public WalkFlow onWalkStart(WalkEvent walkEvent) {
+ @SuppressWarnings("unchecked")
+ List<WalkEvent> propertyKeywords = (List<WalkEvent>) walkEvent.getExecutionContext().getCollectorContext()
+ .getCollectorMap().computeIfAbsent("propertyKeywords", key -> new ArrayList<>());
+ propertyKeywords.add(walkEvent);
+ return WalkFlow.CONTINUE;
+ }
+
+ @Override
+ public void onWalkEnd(WalkEvent walkEvent, Set<ValidationMessage> validationMessages) {
+ }
+ });
+ JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V7).getSchema(schemaData, config);
+ String inputData = "{\r\n"
+ + " \"tags\": [\r\n"
+ + " {\r\n"
+ + " \"name\": \"image\",\r\n"
+ + " \"description\": \"An image\"\r\n"
+ + " },\r\n"
+ + " {\r\n"
+ + " \"name\": \"link\",\r\n"
+ + " \"description\": \"A link\"\r\n"
+ + " }\r\n"
+ + " ]\r\n"
+ + "}";
+ ValidationResult result = schema.walk(inputData, InputFormat.JSON, true);
+ assertTrue(result.getValidationMessages().isEmpty());
+ @SuppressWarnings("unchecked")
+ List<WalkEvent> propertyKeywords = (List<WalkEvent>) result.getExecutionContext().getCollectorContext().get("propertyKeywords");
+ assertEquals(3, propertyKeywords.size());
+ assertEquals("properties", propertyKeywords.get(0).getValidator().getKeyword());
+ assertEquals("", propertyKeywords.get(0).getInstanceLocation().toString());
+ assertEquals("/properties", propertyKeywords.get(0).getSchema().getEvaluationPath()
+ .append(propertyKeywords.get(0).getKeyword()).toString());
+ assertEquals("/tags/0", propertyKeywords.get(1).getInstanceLocation().toString());
+ assertEquals("image", propertyKeywords.get(1).getInstanceNode().get("name").asText());
+ assertEquals("/properties/tags/items/$ref/properties",
+ propertyKeywords.get(1).getValidator().getEvaluationPath().toString());
+ assertEquals("/properties/tags/items/$ref/properties", propertyKeywords.get(1).getSchema().getEvaluationPath()
+ .append(propertyKeywords.get(1).getKeyword()).toString());
+ assertEquals("/tags/1", propertyKeywords.get(2).getInstanceLocation().toString());
+ assertEquals("/properties/tags/items/$ref/properties", propertyKeywords.get(2).getSchema().getEvaluationPath()
+ .append(propertyKeywords.get(2).getKeyword()).toString());
+ assertEquals("link", propertyKeywords.get(2).getInstanceNode().get("name").asText());
+ }
+
+ @Test
+ void propertyListener() {
+ String schemaData = "{\r\n"
+ + " \"$schema\": \"http://json-schema.org/draft-07/schema#\",\r\n"
+ + " \"type\": \"object\",\r\n"
+ + " \"description\": \"Default Description\",\r\n"
+ + " \"properties\": {\r\n"
+ + " \"tags\": {\r\n"
+ + " \"type\": \"array\",\r\n"
+ + " \"items\": {\r\n"
+ + " \"$ref\": \"#/definitions/tag\"\r\n"
+ + " }\r\n"
+ + " }\r\n"
+ + " },\r\n"
+ + " \"definitions\": {\r\n"
+ + " \"tag\": {\r\n"
+ + " \"properties\": {\r\n"
+ + " \"name\": {\r\n"
+ + " \"type\": \"string\"\r\n"
+ + " },\r\n"
+ + " \"description\": {\r\n"
+ + " \"type\": \"string\"\r\n"
+ + " }\r\n"
+ + " }\r\n"
+ + " }\r\n"
+ + " }\r\n"
+ + "}";
+
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setPathType(PathType.JSON_POINTER);
+ config.addPropertyWalkListener(new JsonSchemaWalkListener() {
+
+ @Override
+ public WalkFlow onWalkStart(WalkEvent walkEvent) {
+ @SuppressWarnings("unchecked")
+ List<WalkEvent> properties = (List<WalkEvent>) walkEvent.getExecutionContext().getCollectorContext()
+ .getCollectorMap().computeIfAbsent("properties", key -> new ArrayList<>());
+ properties.add(walkEvent);
+ return WalkFlow.CONTINUE;
+ }
+
+ @Override
+ public void onWalkEnd(WalkEvent walkEvent, Set<ValidationMessage> validationMessages) {
+ }
+ });
+ JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V7).getSchema(schemaData, config);
+ String inputData = "{\r\n"
+ + " \"tags\": [\r\n"
+ + " {\r\n"
+ + " \"name\": \"image\",\r\n"
+ + " \"description\": \"An image\"\r\n"
+ + " },\r\n"
+ + " {\r\n"
+ + " \"name\": \"link\",\r\n"
+ + " \"description\": \"A link\"\r\n"
+ + " }\r\n"
+ + " ]\r\n"
+ + "}";
+ ValidationResult result = schema.walk(inputData, InputFormat.JSON, true);
+ assertTrue(result.getValidationMessages().isEmpty());
+
+ @SuppressWarnings("unchecked")
+ List<WalkEvent> properties = (List<WalkEvent>) result.getExecutionContext().getCollectorContext().get("properties");
+ assertEquals(5, properties.size());
+ assertEquals("properties", properties.get(0).getValidator().getKeyword());
+
+ assertEquals("/tags", properties.get(0).getInstanceLocation().toString());
+ assertEquals("/properties/tags", properties.get(0).getSchema().getEvaluationPath().toString());
+
+ assertEquals("/tags/0/name", properties.get(1).getInstanceLocation().toString());
+ assertEquals("image", properties.get(1).getInstanceNode().asText());
+ assertEquals("/properties/tags/items/$ref/properties/name", properties.get(1).getSchema().getEvaluationPath().toString());
+
+ assertEquals("/tags/0/description", properties.get(2).getInstanceLocation().toString());
+ assertEquals("An image", properties.get(2).getInstanceNode().asText());
+ assertEquals("/properties/tags/items/$ref/properties/description", properties.get(2).getSchema().getEvaluationPath().toString());
+
+ assertEquals("/tags/1/name", properties.get(3).getInstanceLocation().toString());
+ assertEquals("link", properties.get(3).getInstanceNode().asText());
+ assertEquals("/properties/tags/items/$ref/properties/name", properties.get(3).getSchema().getEvaluationPath().toString());
+
+ assertEquals("/tags/1/description", properties.get(4).getInstanceLocation().toString());
+ assertEquals("A link", properties.get(4).getInstanceNode().asText());
+ assertEquals("/properties/tags/items/$ref/properties/description", properties.get(4).getSchema().getEvaluationPath().toString());
+ }
+
+ @Test
+ void itemsListener() {
+ String schemaData = "{\r\n"
+ + " \"$schema\": \"http://json-schema.org/draft-07/schema#\",\r\n"
+ + " \"type\": \"object\",\r\n"
+ + " \"description\": \"Default Description\",\r\n"
+ + " \"properties\": {\r\n"
+ + " \"tags\": {\r\n"
+ + " \"type\": \"array\",\r\n"
+ + " \"items\": {\r\n"
+ + " \"$ref\": \"#/definitions/tag\"\r\n"
+ + " }\r\n"
+ + " }\r\n"
+ + " },\r\n"
+ + " \"definitions\": {\r\n"
+ + " \"tag\": {\r\n"
+ + " \"properties\": {\r\n"
+ + " \"name\": {\r\n"
+ + " \"type\": \"string\"\r\n"
+ + " },\r\n"
+ + " \"description\": {\r\n"
+ + " \"type\": \"string\"\r\n"
+ + " }\r\n"
+ + " }\r\n"
+ + " }\r\n"
+ + " }\r\n"
+ + "}";
+
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setPathType(PathType.JSON_POINTER);
+ config.addItemWalkListener(new JsonSchemaWalkListener() {
+
+ @Override
+ public WalkFlow onWalkStart(WalkEvent walkEvent) {
+ @SuppressWarnings("unchecked")
+ List<WalkEvent> items = (List<WalkEvent>) walkEvent.getExecutionContext().getCollectorContext()
+ .getCollectorMap().computeIfAbsent("items", key -> new ArrayList<>());
+ items.add(walkEvent);
+ return WalkFlow.CONTINUE;
+ }
+
+ @Override
+ public void onWalkEnd(WalkEvent walkEvent, Set<ValidationMessage> validationMessages) {
+ }
+ });
+ JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V7).getSchema(schemaData, config);
+ String inputData = "{\r\n"
+ + " \"tags\": [\r\n"
+ + " {\r\n"
+ + " \"name\": \"image\",\r\n"
+ + " \"description\": \"An image\"\r\n"
+ + " },\r\n"
+ + " {\r\n"
+ + " \"name\": \"link\",\r\n"
+ + " \"description\": \"A link\"\r\n"
+ + " }\r\n"
+ + " ]\r\n"
+ + "}";
+ ValidationResult result = schema.walk(inputData, InputFormat.JSON, true);
+ assertTrue(result.getValidationMessages().isEmpty());
+
+ @SuppressWarnings("unchecked")
+ List<WalkEvent> items = (List<WalkEvent>) result.getExecutionContext().getCollectorContext().get("items");
+ assertEquals(2, items.size());
+ assertEquals("items", items.get(0).getValidator().getKeyword());
+ assertTrue(items.get(0).getValidator() instanceof ItemsValidator);
+
+ assertEquals("/tags/0", items.get(0).getInstanceLocation().toString());
+ assertEquals("/properties/tags/items", items.get(0).getSchema().getEvaluationPath().toString());
+
+ assertEquals("/tags/1", items.get(1).getInstanceLocation().toString());
+ assertEquals("/properties/tags/items", items.get(1).getSchema().getEvaluationPath().toString());
+ }
+
+ @Test
+ void items202012Listener() {
+ String schemaData = "{\r\n"
+ + " \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\r\n"
+ + " \"type\": \"object\",\r\n"
+ + " \"description\": \"Default Description\",\r\n"
+ + " \"properties\": {\r\n"
+ + " \"tags\": {\r\n"
+ + " \"type\": \"array\",\r\n"
+ + " \"items\": {\r\n"
+ + " \"$ref\": \"#/definitions/tag\"\r\n"
+ + " }\r\n"
+ + " }\r\n"
+ + " },\r\n"
+ + " \"definitions\": {\r\n"
+ + " \"tag\": {\r\n"
+ + " \"properties\": {\r\n"
+ + " \"name\": {\r\n"
+ + " \"type\": \"string\"\r\n"
+ + " },\r\n"
+ + " \"description\": {\r\n"
+ + " \"type\": \"string\"\r\n"
+ + " }\r\n"
+ + " }\r\n"
+ + " }\r\n"
+ + " }\r\n"
+ + "}";
+
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setPathType(PathType.JSON_POINTER);
+ config.addItemWalkListener(new JsonSchemaWalkListener() {
+
+ @Override
+ public WalkFlow onWalkStart(WalkEvent walkEvent) {
+ @SuppressWarnings("unchecked")
+ List<WalkEvent> items = (List<WalkEvent>) walkEvent.getExecutionContext().getCollectorContext()
+ .getCollectorMap().computeIfAbsent("items", key -> new ArrayList<>());
+ items.add(walkEvent);
+ return WalkFlow.CONTINUE;
+ }
+
+ @Override
+ public void onWalkEnd(WalkEvent walkEvent, Set<ValidationMessage> validationMessages) {
+ }
+ });
+ JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V7).getSchema(schemaData, config);
+ String inputData = "{\r\n"
+ + " \"tags\": [\r\n"
+ + " {\r\n"
+ + " \"name\": \"image\",\r\n"
+ + " \"description\": \"An image\"\r\n"
+ + " },\r\n"
+ + " {\r\n"
+ + " \"name\": \"link\",\r\n"
+ + " \"description\": \"A link\"\r\n"
+ + " }\r\n"
+ + " ]\r\n"
+ + "}";
+ ValidationResult result = schema.walk(inputData, InputFormat.JSON, true);
+ assertTrue(result.getValidationMessages().isEmpty());
+
+ @SuppressWarnings("unchecked")
+ List<WalkEvent> items = (List<WalkEvent>) result.getExecutionContext().getCollectorContext().get("items");
+ assertEquals(2, items.size());
+ assertEquals("items", items.get(0).getValidator().getKeyword());
+ assertTrue(items.get(0).getValidator() instanceof ItemsValidator202012);
+
+ assertEquals("/tags/0", items.get(0).getInstanceLocation().toString());
+ assertEquals("/properties/tags/items", items.get(0).getSchema().getEvaluationPath().toString());
+
+ assertEquals("/tags/1", items.get(1).getInstanceLocation().toString());
+ assertEquals("/properties/tags/items", items.get(1).getSchema().getEvaluationPath().toString());
+ }
+
+ @Test
+ void draft201909() {
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setPathType(PathType.JSON_POINTER);
+ config.addKeywordWalkListener(ValidatorTypeCode.PROPERTIES.getValue(), new JsonSchemaWalkListener() {
+
+ @Override
+ public WalkFlow onWalkStart(WalkEvent walkEvent) {
+ @SuppressWarnings("unchecked")
+ List<WalkEvent> propertyKeywords = (List<WalkEvent>) walkEvent.getExecutionContext().getCollectorContext()
+ .getCollectorMap().computeIfAbsent("propertyKeywords", key -> new ArrayList<>());
+ propertyKeywords.add(walkEvent);
+ return WalkFlow.CONTINUE;
+ }
+
+ @Override
+ public void onWalkEnd(WalkEvent walkEvent, Set<ValidationMessage> validationMessages) {
+ }
+ });
+ JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V201909)
+ .getSchema(SchemaLocation.of(SchemaId.V201909), config);
+ String inputData = "{\r\n"
+ + " \"$schema\": \"https://json-schema.org/draft/2019-09/schema\",\r\n"
+ + " \"type\": \"object\",\r\n"
+ + " \"properties\": {\r\n"
+ + " \"kebab-case\": {\r\n"
+ + " \"type\": \"string\"\r\n"
+ + " },\r\n"
+ + " \"snake_case\": {\r\n"
+ + " \"type\": \"string\"\r\n"
+ + " },\r\n"
+ + " \"a\": {\r\n"
+ + " \"type\": \"string\"\r\n"
+ + " }\r\n"
+ + " }\r\n"
+ + "}";
+ ValidationResult result = schema.walk(inputData, InputFormat.JSON, true);
+ assertTrue(result.getValidationMessages().isEmpty());
+
+ @SuppressWarnings("unchecked")
+ List<WalkEvent> propertyKeywords = (List<WalkEvent>) result.getExecutionContext().getCollectorContext().getCollectorMap().get("propertyKeywords");
+
+ assertEquals(28, propertyKeywords.size());
+
+ assertEquals("", propertyKeywords.get(0).getInstanceLocation().toString());
+ assertEquals("/properties", propertyKeywords.get(0).getSchema().getEvaluationPath().append(propertyKeywords.get(0).getKeyword()).toString());
+ assertEquals("https://json-schema.org/draft/2019-09/schema#/properties", propertyKeywords.get(0).getSchema().getSchemaLocation().append(propertyKeywords.get(0).getKeyword()).toString());
+
+ assertEquals("", propertyKeywords.get(1).getInstanceLocation().toString());
+ assertEquals("/allOf/0/$ref/properties", propertyKeywords.get(1).getSchema().getEvaluationPath().append(propertyKeywords.get(1).getKeyword()).toString());
+ assertEquals("https://json-schema.org/draft/2019-09/meta/core#/properties", propertyKeywords.get(1).getSchema().getSchemaLocation().append(propertyKeywords.get(1).getKeyword()).toString());
+
+ assertEquals("", propertyKeywords.get(2).getInstanceLocation().toString());
+ assertEquals("/allOf/1/$ref/properties", propertyKeywords.get(2).getSchema().getEvaluationPath().append(propertyKeywords.get(2).getKeyword()).toString());
+ assertEquals("https://json-schema.org/draft/2019-09/meta/applicator#/properties", propertyKeywords.get(2).getSchema().getSchemaLocation().append(propertyKeywords.get(2).getKeyword()).toString());
+
+ assertEquals("/properties/kebab-case", propertyKeywords.get(3).getInstanceLocation().toString());
+ assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/properties", propertyKeywords.get(3).getSchema().getEvaluationPath().append(propertyKeywords.get(3).getKeyword()).toString());
+ assertEquals("https://json-schema.org/draft/2019-09/schema#/properties", propertyKeywords.get(3).getSchema().getSchemaLocation().append(propertyKeywords.get(3).getKeyword()).toString());
+
+ assertEquals("/properties/kebab-case", propertyKeywords.get(4).getInstanceLocation().toString());
+ assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/0/$ref/properties", propertyKeywords.get(4).getSchema().getEvaluationPath().append(propertyKeywords.get(4).getKeyword()).toString());
+ assertEquals("https://json-schema.org/draft/2019-09/meta/core#/properties", propertyKeywords.get(4).getSchema().getSchemaLocation().append(propertyKeywords.get(4).getKeyword()).toString());
+
+ assertEquals("/properties/kebab-case", propertyKeywords.get(5).getInstanceLocation().toString());
+ assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/1/$ref/properties", propertyKeywords.get(5).getSchema().getEvaluationPath().append(propertyKeywords.get(5).getKeyword()).toString());
+ assertEquals("https://json-schema.org/draft/2019-09/meta/applicator#/properties", propertyKeywords.get(5).getSchema().getSchemaLocation().append(propertyKeywords.get(5).getKeyword()).toString());
+
+ assertEquals("/properties/kebab-case", propertyKeywords.get(6).getInstanceLocation().toString());
+ assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/2/$ref/properties", propertyKeywords.get(6).getSchema().getEvaluationPath().append(propertyKeywords.get(6).getKeyword()).toString());
+ assertEquals("https://json-schema.org/draft/2019-09/meta/validation#/properties", propertyKeywords.get(6).getSchema().getSchemaLocation().append(propertyKeywords.get(6).getKeyword()).toString());
+
+ assertEquals("/properties/kebab-case", propertyKeywords.get(7).getInstanceLocation().toString());
+ assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/3/$ref/properties", propertyKeywords.get(7).getSchema().getEvaluationPath().append(propertyKeywords.get(7).getKeyword()).toString());
+ assertEquals("https://json-schema.org/draft/2019-09/meta/meta-data#/properties", propertyKeywords.get(7).getSchema().getSchemaLocation().append(propertyKeywords.get(7).getKeyword()).toString());
+
+ assertEquals("/properties/kebab-case", propertyKeywords.get(8).getInstanceLocation().toString());
+ assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/4/$ref/properties", propertyKeywords.get(8).getSchema().getEvaluationPath().append(propertyKeywords.get(8).getKeyword()).toString());
+ assertEquals("https://json-schema.org/draft/2019-09/meta/format#/properties", propertyKeywords.get(8).getSchema().getSchemaLocation().append(propertyKeywords.get(8).getKeyword()).toString());
+
+ assertEquals("/properties/kebab-case", propertyKeywords.get(9).getInstanceLocation().toString());
+ assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/5/$ref/properties", propertyKeywords.get(9).getSchema().getEvaluationPath().append(propertyKeywords.get(9).getKeyword()).toString());
+ assertEquals("https://json-schema.org/draft/2019-09/meta/content#/properties", propertyKeywords.get(9).getSchema().getSchemaLocation().append(propertyKeywords.get(9).getKeyword()).toString());
+
+ assertEquals("/properties/snake_case", propertyKeywords.get(10).getInstanceLocation().toString());
+ assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/properties", propertyKeywords.get(10).getSchema().getEvaluationPath().append(propertyKeywords.get(10).getKeyword()).toString());
+ assertEquals("https://json-schema.org/draft/2019-09/schema#/properties", propertyKeywords.get(10).getSchema().getSchemaLocation().append(propertyKeywords.get(10).getKeyword()).toString());
+
+ assertEquals("/properties/snake_case", propertyKeywords.get(11).getInstanceLocation().toString());
+ assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/0/$ref/properties", propertyKeywords.get(11).getSchema().getEvaluationPath().append(propertyKeywords.get(11).getKeyword()).toString());
+ assertEquals("https://json-schema.org/draft/2019-09/meta/core#/properties", propertyKeywords.get(11).getSchema().getSchemaLocation().append(propertyKeywords.get(11).getKeyword()).toString());
+
+ assertEquals("/properties/snake_case", propertyKeywords.get(12).getInstanceLocation().toString());
+ assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/1/$ref/properties", propertyKeywords.get(12).getSchema().getEvaluationPath().append(propertyKeywords.get(12).getKeyword()).toString());
+ assertEquals("https://json-schema.org/draft/2019-09/meta/applicator#/properties", propertyKeywords.get(12).getSchema().getSchemaLocation().append(propertyKeywords.get(12).getKeyword()).toString());
+
+ assertEquals("/properties/snake_case", propertyKeywords.get(13).getInstanceLocation().toString());
+ assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/2/$ref/properties", propertyKeywords.get(13).getSchema().getEvaluationPath().append(propertyKeywords.get(13).getKeyword()).toString());
+ assertEquals("https://json-schema.org/draft/2019-09/meta/validation#/properties", propertyKeywords.get(13).getSchema().getSchemaLocation().append(propertyKeywords.get(13).getKeyword()).toString());
+
+ assertEquals("/properties/snake_case", propertyKeywords.get(14).getInstanceLocation().toString());
+ assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/3/$ref/properties", propertyKeywords.get(14).getSchema().getEvaluationPath().append(propertyKeywords.get(14).getKeyword()).toString());
+ assertEquals("https://json-schema.org/draft/2019-09/meta/meta-data#/properties", propertyKeywords.get(14).getSchema().getSchemaLocation().append(propertyKeywords.get(14).getKeyword()).toString());
+
+ assertEquals("/properties/snake_case", propertyKeywords.get(15).getInstanceLocation().toString());
+ assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/4/$ref/properties", propertyKeywords.get(15).getSchema().getEvaluationPath().append(propertyKeywords.get(15).getKeyword()).toString());
+ assertEquals("https://json-schema.org/draft/2019-09/meta/format#/properties", propertyKeywords.get(15).getSchema().getSchemaLocation().append(propertyKeywords.get(15).getKeyword()).toString());
+
+ assertEquals("/properties/snake_case", propertyKeywords.get(16).getInstanceLocation().toString());
+ assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/5/$ref/properties", propertyKeywords.get(16).getSchema().getEvaluationPath().append(propertyKeywords.get(16).getKeyword()).toString());
+ assertEquals("https://json-schema.org/draft/2019-09/meta/content#/properties", propertyKeywords.get(16).getSchema().getSchemaLocation().append(propertyKeywords.get(16).getKeyword()).toString());
+
+ assertEquals("/properties/a", propertyKeywords.get(17).getInstanceLocation().toString());
+ assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/properties", propertyKeywords.get(17).getSchema().getEvaluationPath().append(propertyKeywords.get(17).getKeyword()).toString());
+ assertEquals("https://json-schema.org/draft/2019-09/schema#/properties", propertyKeywords.get(17).getSchema().getSchemaLocation().append(propertyKeywords.get(17).getKeyword()).toString());
+
+ assertEquals("/properties/a", propertyKeywords.get(18).getInstanceLocation().toString());
+ assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/0/$ref/properties", propertyKeywords.get(18).getSchema().getEvaluationPath().append(propertyKeywords.get(18).getKeyword()).toString());
+ assertEquals("https://json-schema.org/draft/2019-09/meta/core#/properties", propertyKeywords.get(18).getSchema().getSchemaLocation().append(propertyKeywords.get(18).getKeyword()).toString());
+
+ assertEquals("/properties/a", propertyKeywords.get(19).getInstanceLocation().toString());
+ assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/1/$ref/properties", propertyKeywords.get(19).getSchema().getEvaluationPath().append(propertyKeywords.get(19).getKeyword()).toString());
+ assertEquals("https://json-schema.org/draft/2019-09/meta/applicator#/properties", propertyKeywords.get(19).getSchema().getSchemaLocation().append(propertyKeywords.get(19).getKeyword()).toString());
+
+ assertEquals("/properties/a", propertyKeywords.get(20).getInstanceLocation().toString());
+ assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/2/$ref/properties", propertyKeywords.get(20).getSchema().getEvaluationPath().append(propertyKeywords.get(20).getKeyword()).toString());
+ assertEquals("https://json-schema.org/draft/2019-09/meta/validation#/properties", propertyKeywords.get(20).getSchema().getSchemaLocation().append(propertyKeywords.get(20).getKeyword()).toString());
+
+ assertEquals("/properties/a", propertyKeywords.get(21).getInstanceLocation().toString());
+ assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/3/$ref/properties", propertyKeywords.get(21).getSchema().getEvaluationPath().append(propertyKeywords.get(21).getKeyword()).toString());
+ assertEquals("https://json-schema.org/draft/2019-09/meta/meta-data#/properties", propertyKeywords.get(21).getSchema().getSchemaLocation().append(propertyKeywords.get(21).getKeyword()).toString());
+
+ assertEquals("/properties/a", propertyKeywords.get(22).getInstanceLocation().toString());
+ assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/4/$ref/properties", propertyKeywords.get(22).getSchema().getEvaluationPath().append(propertyKeywords.get(22).getKeyword()).toString());
+ assertEquals("https://json-schema.org/draft/2019-09/meta/format#/properties", propertyKeywords.get(22).getSchema().getSchemaLocation().append(propertyKeywords.get(22).getKeyword()).toString());
+
+ assertEquals("/properties/a", propertyKeywords.get(23).getInstanceLocation().toString());
+ assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/5/$ref/properties", propertyKeywords.get(23).getSchema().getEvaluationPath().append(propertyKeywords.get(23).getKeyword()).toString());
+ assertEquals("https://json-schema.org/draft/2019-09/meta/content#/properties", propertyKeywords.get(23).getSchema().getSchemaLocation().append(propertyKeywords.get(23).getKeyword()).toString());
+
+ assertEquals("", propertyKeywords.get(24).getInstanceLocation().toString());
+ assertEquals("/allOf/2/$ref/properties", propertyKeywords.get(24).getSchema().getEvaluationPath().append(propertyKeywords.get(24).getKeyword()).toString());
+ assertEquals("https://json-schema.org/draft/2019-09/meta/validation#/properties", propertyKeywords.get(24).getSchema().getSchemaLocation().append(propertyKeywords.get(24).getKeyword()).toString());
+
+ assertEquals("", propertyKeywords.get(25).getInstanceLocation().toString());
+ assertEquals("/allOf/3/$ref/properties", propertyKeywords.get(25).getSchema().getEvaluationPath().append(propertyKeywords.get(25).getKeyword()).toString());
+ assertEquals("https://json-schema.org/draft/2019-09/meta/meta-data#/properties", propertyKeywords.get(25).getSchema().getSchemaLocation().append(propertyKeywords.get(25).getKeyword()).toString());
+
+ assertEquals("", propertyKeywords.get(26).getInstanceLocation().toString());
+ assertEquals("/allOf/4/$ref/properties", propertyKeywords.get(26).getSchema().getEvaluationPath().append(propertyKeywords.get(26).getKeyword()).toString());
+ assertEquals("https://json-schema.org/draft/2019-09/meta/format#/properties", propertyKeywords.get(26).getSchema().getSchemaLocation().append(propertyKeywords.get(26).getKeyword()).toString());
+
+ assertEquals("", propertyKeywords.get(27).getInstanceLocation().toString());
+ assertEquals("/allOf/5/$ref/properties", propertyKeywords.get(27).getSchema().getEvaluationPath().append(propertyKeywords.get(27).getKeyword()).toString());
+ assertEquals("https://json-schema.org/draft/2019-09/meta/content#/properties", propertyKeywords.get(27).getSchema().getSchemaLocation().append(propertyKeywords.get(27).getKeyword()).toString());
+ }
+
+ @Test
+ void applyDefaults() throws JsonMappingException, JsonProcessingException {
+ String schemaData = "{\r\n"
+ + " \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\r\n"
+ + " \"title\": \"\",\r\n"
+ + " \"type\": \"object\",\r\n"
+ + " \"properties\": {\r\n"
+ + " \"s\": {\r\n"
+ + " \"type\": \"string\",\r\n"
+ + " \"default\": \"S\"\r\n"
+ + " },\r\n"
+ + " \"ref\": { \"$ref\": \"#/$defs/r\" }\r\n"
+ + " },\r\n"
+ + " \"required\": [ \"s\", \"ref\" ],\r\n"
+ + "\r\n"
+ + " \"$defs\": {\r\n"
+ + " \"r\": {\r\n"
+ + " \"type\": \"string\",\r\n"
+ + " \"default\": \"REF\"\r\n"
+ + " }\r\n"
+ + " }\r\n"
+ + "}";
+
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setApplyDefaultsStrategy(new ApplyDefaultsStrategy(true, true, true));
+ JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData, config);
+ JsonNode inputNode = JsonMapperFactory.getInstance().readTree("{}");
+ ValidationResult result = schema.walk(inputNode, true);
+ assertEquals("{\"s\":\"S\",\"ref\":\"REF\"}", inputNode.toString());
+ assertTrue(result.getValidationMessages().isEmpty());
+ }
+
+ @Test
+ void applyDefaultsWithWalker() throws JsonMappingException, JsonProcessingException {
+ String schemaData = "{\r\n"
+ + " \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\r\n"
+ + " \"title\": \"\",\r\n"
+ + " \"type\": \"object\",\r\n"
+ + " \"properties\": {\r\n"
+ + " \"s\": {\r\n"
+ + " \"type\": \"string\",\r\n"
+ + " \"default\": \"S\"\r\n"
+ + " },\r\n"
+ + " \"ref\": { \"$ref\": \"#/$defs/r\" }\r\n"
+ + " },\r\n"
+ + " \"required\": [ \"s\", \"ref\" ],\r\n"
+ + "\r\n"
+ + " \"$defs\": {\r\n"
+ + " \"r\": {\r\n"
+ + " \"type\": \"string\",\r\n"
+ + " \"default\": \"REF\"\r\n"
+ + " }\r\n"
+ + " }\r\n"
+ + "}";
+
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.addPropertyWalkListener(new JsonSchemaWalkListener() {
+
+ @Override
+ public WalkFlow onWalkStart(WalkEvent walkEvent) {
+ if (walkEvent.getInstanceNode() == null || walkEvent.getInstanceNode().isMissingNode()
+ || walkEvent.getInstanceNode().isNull()) {
+ JsonSchema schema = walkEvent.getSchema();
+ JsonSchemaRef schemaRef = JsonSchemaRefs.from(schema);
+ if (schemaRef != null) {
+ schema = schemaRef.getSchema();
+ }
+ JsonNode defaultNode = schema.getSchemaNode().get("default");
+ if (defaultNode != null) {
+ ObjectNode parentNode = (ObjectNode) JsonNodes.get(walkEvent.getRootNode(),
+ walkEvent.getInstanceLocation().getParent());
+ parentNode.set(walkEvent.getInstanceLocation().getName(-1), defaultNode);
+ }
+ }
+ return WalkFlow.CONTINUE;
+ }
+
+ @Override
+ public void onWalkEnd(WalkEvent walkEvent, Set<ValidationMessage> validationMessages) {
+ }
+ });
+
+ JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData, config);
+ JsonNode inputNode = JsonMapperFactory.getInstance().readTree("{}");
+ ValidationResult result = schema.walk(inputNode, true);
+ assertEquals("{\"s\":\"S\",\"ref\":\"REF\"}", inputNode.toString());
+ assertTrue(result.getValidationMessages().isEmpty());
+ }
+
+ @Test
+ void applyInvalidDefaultsWithWalker() throws JsonMappingException, JsonProcessingException {
+ String schemaData = "{\r\n"
+ + " \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\r\n"
+ + " \"title\": \"\",\r\n"
+ + " \"type\": \"object\",\r\n"
+ + " \"properties\": {\r\n"
+ + " \"s\": {\r\n"
+ + " \"type\": \"string\",\r\n"
+ + " \"default\": 1\r\n"
+ + " },\r\n"
+ + " \"ref\": { \"$ref\": \"#/$defs/r\" }\r\n"
+ + " },\r\n"
+ + " \"required\": [ \"s\", \"ref\" ],\r\n"
+ + "\r\n"
+ + " \"$defs\": {\r\n"
+ + " \"r\": {\r\n"
+ + " \"type\": \"string\",\r\n"
+ + " \"default\": \"REF\"\r\n"
+ + " }\r\n"
+ + " }\r\n"
+ + "}";
+
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.addPropertyWalkListener(new JsonSchemaWalkListener() {
+
+ @Override
+ public WalkFlow onWalkStart(WalkEvent walkEvent) {
+ if (walkEvent.getInstanceNode() == null || walkEvent.getInstanceNode().isMissingNode()
+ || walkEvent.getInstanceNode().isNull()) {
+ JsonSchema schema = walkEvent.getSchema();
+ JsonSchemaRef schemaRef = JsonSchemaRefs.from(schema);
+ if (schemaRef != null) {
+ schema = schemaRef.getSchema();
+ }
+ JsonNode defaultNode = schema.getSchemaNode().get("default");
+ if (defaultNode != null) {
+ ObjectNode parentNode = (ObjectNode) JsonNodes.get(walkEvent.getRootNode(),
+ walkEvent.getInstanceLocation().getParent());
+ parentNode.set(walkEvent.getInstanceLocation().getName(-1), defaultNode);
+ }
+ }
+ return WalkFlow.CONTINUE;
+ }
+
+ @Override
+ public void onWalkEnd(WalkEvent walkEvent, Set<ValidationMessage> validationMessages) {
+ }
+ });
+
+ JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData, config);
+ JsonNode inputNode = JsonMapperFactory.getInstance().readTree("{}");
+ ValidationResult result = schema.walk(inputNode, true);
+ assertEquals("{\"s\":1,\"ref\":\"REF\"}", inputNode.toString());
+ assertFalse(result.getValidationMessages().isEmpty());
+
+ inputNode = JsonMapperFactory.getInstance().readTree("{}");
+ result = schema.walk(inputNode, false);
+ assertEquals("{\"s\":1,\"ref\":\"REF\"}", inputNode.toString());
+ assertTrue(result.getValidationMessages().isEmpty());
+ }
+
+ @Test
+ void missingRequired() throws JsonMappingException, JsonProcessingException {
+ String schemaData = "{\r\n"
+ + " \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\r\n"
+ + " \"title\": \"\",\r\n"
+ + " \"type\": \"object\",\r\n"
+ + " \"properties\": {\r\n"
+ + " \"s\": {\r\n"
+ + " \"type\": \"integer\"\r\n"
+ + " },\r\n"
+ + " \"ref\": { \"$ref\": \"#/$defs/r\" }\r\n"
+ + " },\r\n"
+ + " \"required\": [ \"s\", \"ref\" ],\r\n"
+ + "\r\n"
+ + " \"$defs\": {\r\n"
+ + " \"r\": {\r\n"
+ + " \"type\": \"string\"\r\n"
+ + " }\r\n"
+ + " }\r\n"
+ + "}";
+ Map<String, JsonNode> missingSchemaNode = new LinkedHashMap<>();
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.addKeywordWalkListener(ValidatorTypeCode.PROPERTIES.getValue(), new JsonSchemaWalkListener() {
+
+ @Override
+ public WalkFlow onWalkStart(WalkEvent walkEvent) {
+ JsonNode requiredNode = walkEvent.getSchema().getSchemaNode().get("required");
+ List<String> requiredProperties = new ArrayList<>();
+ if (requiredNode != null) {
+ if (requiredNode.isArray()) {
+ for (JsonNode fieldName : requiredNode) {
+ requiredProperties.add(fieldName.asText());
+ }
+ }
+ }
+ for (String requiredProperty : requiredProperties) {
+ JsonNode propertyNode = walkEvent.getInstanceNode().get(requiredProperty);
+ if (propertyNode == null) {
+ // Get the schema
+ PropertiesValidator propertiesValidator = walkEvent.getValidator();
+ JsonSchema propertySchema = propertiesValidator.getSchemas().get(requiredProperty);
+ JsonSchemaRef schemaRef = JsonSchemaRefs.from(propertySchema);
+ if (schemaRef != null) {
+ propertySchema = schemaRef.getSchema();
+ }
+ missingSchemaNode.put(requiredProperty, propertySchema.getSchemaNode());
+ }
+ }
+ return WalkFlow.CONTINUE;
+ }
+
+ @Override
+ public void onWalkEnd(WalkEvent walkEvent, Set<ValidationMessage> validationMessages) {
+ }
+ });
+
+ JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData, config);
+ JsonNode inputNode = JsonMapperFactory.getInstance().readTree("{}");
+ ValidationResult result = schema.walk(inputNode, true);
+ assertFalse(result.getValidationMessages().isEmpty());
+ assertEquals("{\"type\":\"integer\"}", missingSchemaNode.get("s").toString());
+ assertEquals("{\"type\":\"string\"}", missingSchemaNode.get("ref").toString());
+ }
+
+ @Test
+ void generateDataWithWalker() throws JsonMappingException, JsonProcessingException {
+ Map<String, Supplier<String>> generators = new HashMap<>();
+ generators.put("name.findName", () -> "John Doe");
+ generators.put("internet.email", () -> "john.doe@gmail.com");
+
+ String schemaData = "{\r\n"
+ + " \"type\": \"object\",\r\n"
+ + " \"properties\": {\r\n"
+ + " \"name\": {\r\n"
+ + " \"$ref\": \"#/$defs/Name\"\r\n"
+ + " },\r\n"
+ + " \"email\": {\r\n"
+ + " \"type\": \"string\",\r\n"
+ + " \"faker\": \"internet.email\"\r\n"
+ + " }\r\n"
+ + " },\r\n"
+ + " \"required\": [\r\n"
+ + " \"name\",\r\n"
+ + " \"email\"\r\n"
+ + " ],\r\n"
+ + " \"$defs\": {\r\n"
+ + " \"Name\": {\r\n"
+ + " \"type\": \"string\",\r\n"
+ + " \"faker\": \"name.findName\"\r\n"
+ + " }\r\n"
+ + " }\r\n"
+ + "}";
+
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.addPropertyWalkListener(new JsonSchemaWalkListener() {
+ @Override
+ public WalkFlow onWalkStart(WalkEvent walkEvent) {
+ if (walkEvent.getInstanceNode() == null || walkEvent.getInstanceNode().isMissingNode()
+ || walkEvent.getInstanceNode().isNull()) {
+ JsonSchema schema = walkEvent.getSchema();
+ JsonSchemaRef schemaRef = null;
+ do {
+ schemaRef = JsonSchemaRefs.from(schema);
+ if (schemaRef != null) {
+ schema = schemaRef.getSchema();
+ }
+ } while (schemaRef != null);
+ JsonNode fakerNode = schema.getSchemaNode().get("faker");
+ if (fakerNode != null) {
+ String faker = fakerNode.asText();
+ String fakeData = generators.get(faker).get();
+ JsonNode fakeDataNode = JsonNodeFactory.instance.textNode(fakeData);
+ ObjectNode parentNode = (ObjectNode) JsonNodes.get(walkEvent.getRootNode(),
+ walkEvent.getInstanceLocation().getParent());
+ parentNode.set(walkEvent.getInstanceLocation().getName(-1), fakeDataNode);
+ }
+ }
+ return WalkFlow.CONTINUE;
+ }
+
+ @Override
+ public void onWalkEnd(WalkEvent walkEvent, Set<ValidationMessage> validationMessages) {
+ }
+ });
+
+ JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData, config);
+ JsonNode inputNode = JsonMapperFactory.getInstance().readTree("{}");
+ ValidationResult result = schema.walk(inputNode, true);
+ assertEquals("{\"name\":\"John Doe\",\"email\":\"john.doe@gmail.com\"}", inputNode.toString());
+ assertTrue(result.getValidationMessages().isEmpty());
+ }
+}