Skip to content

Commit

Permalink
Reduce memory usage and improve performance (#966)
Browse files Browse the repository at this point in the history
* Add sets view

* Use set view for aggregating child errors

* Optimize properties validator

* Use set view

* Use set view in all of

* Refactor anyOf

* Use set view in items

* Use set view in properties

* Refactor if else

* Refactor

* Use setview in prefixItems

* Refactor DependenciesValidator

* Refactor dependent schemas

* Refactor

* Refactor

* Refactor
  • Loading branch information
justin-tay authored Feb 13, 2024
1 parent c768bc1 commit 1f60740
Show file tree
Hide file tree
Showing 18 changed files with 556 additions and 105 deletions.
14 changes: 10 additions & 4 deletions src/main/java/com/networknt/schema/AllOfValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.networknt.schema.utils.SetView;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -48,7 +49,7 @@ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNo
// get the Validator state object storing validation data
ValidatorState state = executionContext.getValidatorState();

Set<ValidationMessage> childSchemaErrors = new LinkedHashSet<>();
SetView<ValidationMessage> childSchemaErrors = null;

for (JsonSchema schema : this.schemas) {
Set<ValidationMessage> localErrors = null;
Expand All @@ -58,8 +59,13 @@ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNo
} else {
localErrors = schema.walk(executionContext, node, rootNode, instanceLocation, true);
}

childSchemaErrors.addAll(localErrors);

if (localErrors != null && !localErrors.isEmpty()) {
if (childSchemaErrors == null) {
childSchemaErrors = new SetView<>();
}
childSchemaErrors.union(localErrors);
}

if (this.validationContext.getConfig().isOpenAPI3StyleDiscriminators()) {
final Iterator<JsonNode> arrayElements = this.schemaNode.elements();
Expand Down Expand Up @@ -91,7 +97,7 @@ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNo
}
}

return Collections.unmodifiableSet(childSchemaErrors);
return childSchemaErrors != null ? childSchemaErrors : Collections.emptySet();
}

@Override
Expand Down
56 changes: 28 additions & 28 deletions src/main/java/com/networknt/schema/AnyOfValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@
package com.networknt.schema;

import com.fasterxml.jackson.databind.JsonNode;
import com.networknt.schema.utils.SetView;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.stream.Collectors;

/**
* {@link JsonValidator} for anyOf.
Expand Down Expand Up @@ -64,7 +64,7 @@ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNo

boolean initialHasMatchedNode = state.hasMatchedNode();

Set<ValidationMessage> allErrors = new LinkedHashSet<>();
SetView<ValidationMessage> allErrors = null;

int numberOfValidSubSchemas = 0;
try {
Expand All @@ -82,7 +82,10 @@ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNo
// ignore it
// For union type, it is a must to call TypeValidator
if (typeValidator.getSchemaType() != JsonType.UNION && !typeValidator.equalsToSchemaType(node)) {
allErrors.addAll(typeValidator.validate(executionContext, node, rootNode, instanceLocation));
if (allErrors == null) {
allErrors = new SetView<>();
}
allErrors.union(typeValidator.validate(executionContext, node, rootNode, instanceLocation));
continue;
}
}
Expand All @@ -105,63 +108,60 @@ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNo

if (errors.isEmpty() && (!this.validationContext.getConfig().isOpenAPI3StyleDiscriminators())
&& canShortCircuit() && canShortCircuit(executionContext)) {
// Clear all errors.
allErrors.clear();
// Clear all errors. Note that this is checked in finally.
allErrors = null;
// return empty errors.
return errors;
} else if (this.validationContext.getConfig().isOpenAPI3StyleDiscriminators()) {
if (this.discriminatorContext.isDiscriminatorMatchFound()) {
if (!errors.isEmpty()) {
allErrors.addAll(errors);
allErrors.add(message().instanceNode(node).instanceLocation(instanceLocation)
.locale(executionContext.getExecutionConfig().getLocale())
.failFast(executionContext.isFailFast())
.arguments(DISCRIMINATOR_REMARK).build());
// The following is to match the previous logic adding to all errors
// which is generally discarded as it returns errors but the allErrors
// is getting processed in finally
if (allErrors == null) {
allErrors = new SetView<>();
}
allErrors.union(Collections
.singleton(message().instanceNode(node).instanceLocation(instanceLocation)
.locale(executionContext.getExecutionConfig().getLocale())
.failFast(executionContext.isFailFast()).arguments(DISCRIMINATOR_REMARK)
.build()));
} else {
// Clear all errors.
allErrors.clear();
// Clear all errors. Note that this is checked in finally.
allErrors = null;
}
return errors;
}
}
allErrors.addAll(errors);
if (allErrors == null) {
allErrors = new SetView<>();
}
allErrors.union(errors);
}
} finally {
// Restore flag
executionContext.setFailFast(failFast);
}

// determine only those errors which are NOT of type "required" property missing
Set<ValidationMessage> childNotRequiredErrors = allErrors.stream()
.filter(error -> !ValidatorTypeCode.REQUIRED.getValue().equals(error.getType()))
.collect(Collectors.toCollection(LinkedHashSet::new));

// in case we had at least one (anyOf, i.e. any number >= 1 of) valid subschemas, we can remove all other errors about "required" properties
if (numberOfValidSubSchemas >= 1 && childNotRequiredErrors.isEmpty()) {
allErrors = childNotRequiredErrors;
}

if (this.validationContext.getConfig().isOpenAPI3StyleDiscriminators() && this.discriminatorContext.isActive()) {
final Set<ValidationMessage> errors = new LinkedHashSet<>();
errors.add(message().instanceNode(node).instanceLocation(instanceLocation)
return Collections.singleton(message().instanceNode(node).instanceLocation(instanceLocation)
.locale(executionContext.getExecutionConfig().getLocale())
.arguments(
"based on the provided discriminator. No alternative could be chosen based on the discriminator property")
.build());
return Collections.unmodifiableSet(errors);
}
} finally {
if (this.validationContext.getConfig().isOpenAPI3StyleDiscriminators()) {
executionContext.leaveDiscriminatorContextImmediately(instanceLocation);
}
if (allErrors.isEmpty()) {
if (allErrors == null || allErrors.isEmpty()) {
state.setMatchedNode(true);
}
}
if (numberOfValidSubSchemas >= 1) {
return Collections.emptySet();
}
return Collections.unmodifiableSet(allErrors);
return allErrors != null ? allErrors : Collections.emptySet();
}

@Override
Expand Down
18 changes: 13 additions & 5 deletions src/main/java/com/networknt/schema/DependenciesValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public DependenciesValidator(SchemaLocation schemaLocation, JsonNodePath evaluat
if (pvalue.isArray()) {
List<String> depsProps = propertyDeps.get(pname);
if (depsProps == null) {
depsProps = new ArrayList<String>();
depsProps = new ArrayList<>();
propertyDeps.put(pname, depsProps);
}
for (int i = 0; i < pvalue.size(); i++) {
Expand All @@ -65,14 +65,17 @@ public DependenciesValidator(SchemaLocation schemaLocation, JsonNodePath evaluat
public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
debug(logger, node, rootNode, instanceLocation);

Set<ValidationMessage> errors = new LinkedHashSet<ValidationMessage>();
Set<ValidationMessage> errors = null;

for (Iterator<String> it = node.fieldNames(); it.hasNext(); ) {
String pname = it.next();
List<String> deps = propertyDeps.get(pname);
if (deps != null && !deps.isEmpty()) {
for (String field : deps) {
if (node.get(field) == null) {
if (errors == null) {
errors = new LinkedHashSet<>();
}
errors.add(message().instanceNode(node).property(pname).instanceLocation(instanceLocation)
.locale(executionContext.getExecutionConfig().getLocale())
.failFast(executionContext.isFailFast())
Expand All @@ -82,11 +85,16 @@ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNo
}
JsonSchema schema = schemaDeps.get(pname);
if (schema != null) {
errors.addAll(schema.validate(executionContext, node, rootNode, instanceLocation));
Set<ValidationMessage> schemaDepsErrors = schema.validate(executionContext, node, rootNode, instanceLocation);
if (!schemaDepsErrors.isEmpty()) {
if (errors == null) {
errors = new LinkedHashSet<>();
}
errors.addAll(schemaDepsErrors);
}
}
}

return Collections.unmodifiableSet(errors);
return errors == null || errors.isEmpty() ? Collections.emptySet() : Collections.unmodifiableSet(errors);
}

@Override
Expand Down
14 changes: 9 additions & 5 deletions src/main/java/com/networknt/schema/DependentSchemas.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,17 +47,21 @@ public DependentSchemas(SchemaLocation schemaLocation, JsonNodePath evaluationPa
public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
debug(logger, node, rootNode, instanceLocation);

Set<ValidationMessage> errors = new LinkedHashSet<>();

Set<ValidationMessage> errors = null;
for (Iterator<String> it = node.fieldNames(); it.hasNext(); ) {
String pname = it.next();
JsonSchema schema = this.schemaDependencies.get(pname);
if (schema != null) {
errors.addAll(schema.validate(executionContext, node, rootNode, instanceLocation));
Set<ValidationMessage> schemaDependenciesErrors = schema.validate(executionContext, node, rootNode, instanceLocation);
if (!schemaDependenciesErrors.isEmpty()) {
if (errors == null) {
errors = new LinkedHashSet<>();
}
errors.addAll(schemaDependenciesErrors);
}
}
}

return Collections.unmodifiableSet(errors);
return errors == null || errors.isEmpty() ? Collections.emptySet() : Collections.unmodifiableSet(errors);
}

@Override
Expand Down
7 changes: 3 additions & 4 deletions src/main/java/com/networknt/schema/IfValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ public IfValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, J
@Override
public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
debug(logger, node, rootNode, instanceLocation);
Set<ValidationMessage> errors = new LinkedHashSet<>();

boolean ifConditionPassed = false;

Expand All @@ -81,11 +80,11 @@ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNo
}

if (ifConditionPassed && this.thenSchema != null) {
errors.addAll(this.thenSchema.validate(executionContext, node, rootNode, instanceLocation));
return this.thenSchema.validate(executionContext, node, rootNode, instanceLocation);
} else if (!ifConditionPassed && this.elseSchema != null) {
errors.addAll(this.elseSchema.validate(executionContext, node, rootNode, instanceLocation));
return this.elseSchema.validate(executionContext, node, rootNode, instanceLocation);
}
return Collections.unmodifiableSet(errors);
return Collections.emptySet();
}

@Override
Expand Down
17 changes: 9 additions & 8 deletions src/main/java/com/networknt/schema/ItemsValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.networknt.schema.annotation.JsonNodeAnnotation;
import com.networknt.schema.utils.SetView;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -119,7 +120,7 @@ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNo
}

boolean hasAdditionalItem = false;
Set<ValidationMessage> errors = new LinkedHashSet<>();
SetView<ValidationMessage> errors = new SetView<ValidationMessage>();
if (node.isArray()) {
int i = 0;
for (JsonNode n : node) {
Expand All @@ -143,10 +144,10 @@ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNo
.keyword("additionalItems").value(true).build());
}
}
return errors.isEmpty() ? Collections.emptySet() : Collections.unmodifiableSet(errors);
return errors.isEmpty() ? Collections.emptySet() : errors;
}

private boolean doValidate(ExecutionContext executionContext, Set<ValidationMessage> errors, int i, JsonNode node,
private boolean doValidate(ExecutionContext executionContext, SetView<ValidationMessage> errors, int i, JsonNode node,
JsonNode rootNode, JsonNodePath instanceLocation) {
boolean isAdditionalItem = false;
JsonNodePath path = instanceLocation.append(i);
Expand All @@ -156,14 +157,14 @@ private boolean doValidate(ExecutionContext executionContext, Set<ValidationMess
// schema)
Set<ValidationMessage> results = this.schema.validate(executionContext, node, rootNode, path);
if (!results.isEmpty()) {
errors.addAll(results);
errors.union(results);
}
} else if (this.tupleSchema != null) {
if (i < this.tupleSchema.size()) {
// validate against tuple schema
Set<ValidationMessage> results = this.tupleSchema.get(i).validate(executionContext, node, rootNode, path);
if (!results.isEmpty()) {
errors.addAll(results);
errors.union(results);
}
} else {
if ((this.additionalItems != null && this.additionalItems) || this.additionalSchema != null) {
Expand All @@ -174,21 +175,21 @@ private boolean doValidate(ExecutionContext executionContext, Set<ValidationMess
// validate against additional item schema
Set<ValidationMessage> results = this.additionalSchema.validate(executionContext, node, rootNode, path);
if (!results.isEmpty()) {
errors.addAll(results);
errors.union(results);
}
} else if (this.additionalItems != null) {
if (this.additionalItems) {
// evaluatedItems.add(path);
} else {
// no additional item allowed, return error
errors.add(message().instanceNode(rootNode).instanceLocation(instanceLocation)
errors.union(Collections.singleton(message().instanceNode(rootNode).instanceLocation(instanceLocation)
.type("additionalItems")
.messageKey("additionalItems")
.evaluationPath(this.additionalItemsEvaluationPath)
.schemaLocation(this.additionalItemsSchemaLocation)
.schemaNode(this.additionalItemsSchemaNode)
.locale(executionContext.getExecutionConfig().getLocale())
.failFast(executionContext.isFailFast()).arguments(i).build());
.failFast(executionContext.isFailFast()).arguments(i).build()));
}
}
}
Expand Down
11 changes: 7 additions & 4 deletions src/main/java/com/networknt/schema/ItemsValidator202012.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.networknt.schema.annotation.JsonNodeAnnotation;
import com.networknt.schema.utils.SetView;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -67,8 +68,7 @@ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNo

// ignores non-arrays
if (node.isArray()) {
Set<ValidationMessage> errors = new LinkedHashSet<>();
// Collection<JsonNodePath> evaluatedItems = executionContext.getCollectorContext().getEvaluatedItems();
SetView<ValidationMessage> errors = null;
boolean evaluated = false;
for (int i = this.prefixCount; i < node.size(); ++i) {
JsonNodePath path = instanceLocation.append(i);
Expand All @@ -87,7 +87,10 @@ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNo
if (results.isEmpty()) {
// evaluatedItems.add(path);
} else {
errors.addAll(results);
if (errors == null) {
errors = new SetView<>();
}
errors.union(results);
}
evaluated = true;
}
Expand All @@ -100,7 +103,7 @@ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNo
.keyword(getKeyword()).value(true).build());
}
}
return errors.isEmpty() ? Collections.emptySet() : Collections.unmodifiableSet(errors);
return errors == null || errors.isEmpty() ? Collections.emptySet() : errors;
} else {
return Collections.emptySet();
}
Expand Down
Loading

0 comments on commit 1f60740

Please sign in to comment.