Skip to content

Commit 1f60740

Browse files
authored
Reduce memory usage and improve performance (#966)
* 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
1 parent c768bc1 commit 1f60740

18 files changed

+556
-105
lines changed

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

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import com.fasterxml.jackson.databind.JsonNode;
2222
import com.fasterxml.jackson.databind.node.ObjectNode;
23+
import com.networknt.schema.utils.SetView;
2324

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

51-
Set<ValidationMessage> childSchemaErrors = new LinkedHashSet<>();
52+
SetView<ValidationMessage> childSchemaErrors = null;
5253

5354
for (JsonSchema schema : this.schemas) {
5455
Set<ValidationMessage> localErrors = null;
@@ -58,8 +59,13 @@ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNo
5859
} else {
5960
localErrors = schema.walk(executionContext, node, rootNode, instanceLocation, true);
6061
}
61-
62-
childSchemaErrors.addAll(localErrors);
62+
63+
if (localErrors != null && !localErrors.isEmpty()) {
64+
if (childSchemaErrors == null) {
65+
childSchemaErrors = new SetView<>();
66+
}
67+
childSchemaErrors.union(localErrors);
68+
}
6369

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

94-
return Collections.unmodifiableSet(childSchemaErrors);
100+
return childSchemaErrors != null ? childSchemaErrors : Collections.emptySet();
95101
}
96102

97103
@Override

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

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@
1717
package com.networknt.schema;
1818

1919
import com.fasterxml.jackson.databind.JsonNode;
20+
import com.networknt.schema.utils.SetView;
2021

2122
import org.slf4j.Logger;
2223
import org.slf4j.LoggerFactory;
2324

2425
import java.util.*;
25-
import java.util.stream.Collectors;
2626

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

6565
boolean initialHasMatchedNode = state.hasMatchedNode();
6666

67-
Set<ValidationMessage> allErrors = new LinkedHashSet<>();
67+
SetView<ValidationMessage> allErrors = null;
6868

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

106109
if (errors.isEmpty() && (!this.validationContext.getConfig().isOpenAPI3StyleDiscriminators())
107110
&& canShortCircuit() && canShortCircuit(executionContext)) {
108-
// Clear all errors.
109-
allErrors.clear();
111+
// Clear all errors. Note that this is checked in finally.
112+
allErrors = null;
110113
// return empty errors.
111114
return errors;
112115
} else if (this.validationContext.getConfig().isOpenAPI3StyleDiscriminators()) {
113116
if (this.discriminatorContext.isDiscriminatorMatchFound()) {
114117
if (!errors.isEmpty()) {
115-
allErrors.addAll(errors);
116-
allErrors.add(message().instanceNode(node).instanceLocation(instanceLocation)
117-
.locale(executionContext.getExecutionConfig().getLocale())
118-
.failFast(executionContext.isFailFast())
119-
.arguments(DISCRIMINATOR_REMARK).build());
118+
// The following is to match the previous logic adding to all errors
119+
// which is generally discarded as it returns errors but the allErrors
120+
// is getting processed in finally
121+
if (allErrors == null) {
122+
allErrors = new SetView<>();
123+
}
124+
allErrors.union(Collections
125+
.singleton(message().instanceNode(node).instanceLocation(instanceLocation)
126+
.locale(executionContext.getExecutionConfig().getLocale())
127+
.failFast(executionContext.isFailFast()).arguments(DISCRIMINATOR_REMARK)
128+
.build()));
120129
} else {
121-
// Clear all errors.
122-
allErrors.clear();
130+
// Clear all errors. Note that this is checked in finally.
131+
allErrors = null;
123132
}
124133
return errors;
125134
}
126135
}
127-
allErrors.addAll(errors);
136+
if (allErrors == null) {
137+
allErrors = new SetView<>();
138+
}
139+
allErrors.union(errors);
128140
}
129141
} finally {
130142
// Restore flag
131143
executionContext.setFailFast(failFast);
132144
}
133145

134-
// determine only those errors which are NOT of type "required" property missing
135-
Set<ValidationMessage> childNotRequiredErrors = allErrors.stream()
136-
.filter(error -> !ValidatorTypeCode.REQUIRED.getValue().equals(error.getType()))
137-
.collect(Collectors.toCollection(LinkedHashSet::new));
138-
139-
// 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
140-
if (numberOfValidSubSchemas >= 1 && childNotRequiredErrors.isEmpty()) {
141-
allErrors = childNotRequiredErrors;
142-
}
143-
144146
if (this.validationContext.getConfig().isOpenAPI3StyleDiscriminators() && this.discriminatorContext.isActive()) {
145-
final Set<ValidationMessage> errors = new LinkedHashSet<>();
146-
errors.add(message().instanceNode(node).instanceLocation(instanceLocation)
147+
return Collections.singleton(message().instanceNode(node).instanceLocation(instanceLocation)
147148
.locale(executionContext.getExecutionConfig().getLocale())
148149
.arguments(
149150
"based on the provided discriminator. No alternative could be chosen based on the discriminator property")
150151
.build());
151-
return Collections.unmodifiableSet(errors);
152152
}
153153
} finally {
154154
if (this.validationContext.getConfig().isOpenAPI3StyleDiscriminators()) {
155155
executionContext.leaveDiscriminatorContextImmediately(instanceLocation);
156156
}
157-
if (allErrors.isEmpty()) {
157+
if (allErrors == null || allErrors.isEmpty()) {
158158
state.setMatchedNode(true);
159159
}
160160
}
161161
if (numberOfValidSubSchemas >= 1) {
162162
return Collections.emptySet();
163163
}
164-
return Collections.unmodifiableSet(allErrors);
164+
return allErrors != null ? allErrors : Collections.emptySet();
165165
}
166166

167167
@Override

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

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public DependenciesValidator(SchemaLocation schemaLocation, JsonNodePath evaluat
4949
if (pvalue.isArray()) {
5050
List<String> depsProps = propertyDeps.get(pname);
5151
if (depsProps == null) {
52-
depsProps = new ArrayList<String>();
52+
depsProps = new ArrayList<>();
5353
propertyDeps.put(pname, depsProps);
5454
}
5555
for (int i = 0; i < pvalue.size(); i++) {
@@ -65,14 +65,17 @@ public DependenciesValidator(SchemaLocation schemaLocation, JsonNodePath evaluat
6565
public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
6666
debug(logger, node, rootNode, instanceLocation);
6767

68-
Set<ValidationMessage> errors = new LinkedHashSet<ValidationMessage>();
68+
Set<ValidationMessage> errors = null;
6969

7070
for (Iterator<String> it = node.fieldNames(); it.hasNext(); ) {
7171
String pname = it.next();
7272
List<String> deps = propertyDeps.get(pname);
7373
if (deps != null && !deps.isEmpty()) {
7474
for (String field : deps) {
7575
if (node.get(field) == null) {
76+
if (errors == null) {
77+
errors = new LinkedHashSet<>();
78+
}
7679
errors.add(message().instanceNode(node).property(pname).instanceLocation(instanceLocation)
7780
.locale(executionContext.getExecutionConfig().getLocale())
7881
.failFast(executionContext.isFailFast())
@@ -82,11 +85,16 @@ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNo
8285
}
8386
JsonSchema schema = schemaDeps.get(pname);
8487
if (schema != null) {
85-
errors.addAll(schema.validate(executionContext, node, rootNode, instanceLocation));
88+
Set<ValidationMessage> schemaDepsErrors = schema.validate(executionContext, node, rootNode, instanceLocation);
89+
if (!schemaDepsErrors.isEmpty()) {
90+
if (errors == null) {
91+
errors = new LinkedHashSet<>();
92+
}
93+
errors.addAll(schemaDepsErrors);
94+
}
8695
}
8796
}
88-
89-
return Collections.unmodifiableSet(errors);
97+
return errors == null || errors.isEmpty() ? Collections.emptySet() : Collections.unmodifiableSet(errors);
9098
}
9199

92100
@Override

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

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,17 +47,21 @@ public DependentSchemas(SchemaLocation schemaLocation, JsonNodePath evaluationPa
4747
public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
4848
debug(logger, node, rootNode, instanceLocation);
4949

50-
Set<ValidationMessage> errors = new LinkedHashSet<>();
51-
50+
Set<ValidationMessage> errors = null;
5251
for (Iterator<String> it = node.fieldNames(); it.hasNext(); ) {
5352
String pname = it.next();
5453
JsonSchema schema = this.schemaDependencies.get(pname);
5554
if (schema != null) {
56-
errors.addAll(schema.validate(executionContext, node, rootNode, instanceLocation));
55+
Set<ValidationMessage> schemaDependenciesErrors = schema.validate(executionContext, node, rootNode, instanceLocation);
56+
if (!schemaDependenciesErrors.isEmpty()) {
57+
if (errors == null) {
58+
errors = new LinkedHashSet<>();
59+
}
60+
errors.addAll(schemaDependenciesErrors);
61+
}
5762
}
5863
}
59-
60-
return Collections.unmodifiableSet(errors);
64+
return errors == null || errors.isEmpty() ? Collections.emptySet() : Collections.unmodifiableSet(errors);
6165
}
6266

6367
@Override

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

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@ public IfValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, J
6666
@Override
6767
public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
6868
debug(logger, node, rootNode, instanceLocation);
69-
Set<ValidationMessage> errors = new LinkedHashSet<>();
7069

7170
boolean ifConditionPassed = false;
7271

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

8382
if (ifConditionPassed && this.thenSchema != null) {
84-
errors.addAll(this.thenSchema.validate(executionContext, node, rootNode, instanceLocation));
83+
return this.thenSchema.validate(executionContext, node, rootNode, instanceLocation);
8584
} else if (!ifConditionPassed && this.elseSchema != null) {
86-
errors.addAll(this.elseSchema.validate(executionContext, node, rootNode, instanceLocation));
85+
return this.elseSchema.validate(executionContext, node, rootNode, instanceLocation);
8786
}
88-
return Collections.unmodifiableSet(errors);
87+
return Collections.emptySet();
8988
}
9089

9190
@Override

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

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import com.fasterxml.jackson.databind.JsonNode;
2020
import com.fasterxml.jackson.databind.node.ArrayNode;
2121
import com.networknt.schema.annotation.JsonNodeAnnotation;
22+
import com.networknt.schema.utils.SetView;
2223

2324
import org.slf4j.Logger;
2425
import org.slf4j.LoggerFactory;
@@ -119,7 +120,7 @@ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNo
119120
}
120121

121122
boolean hasAdditionalItem = false;
122-
Set<ValidationMessage> errors = new LinkedHashSet<>();
123+
SetView<ValidationMessage> errors = new SetView<ValidationMessage>();
123124
if (node.isArray()) {
124125
int i = 0;
125126
for (JsonNode n : node) {
@@ -143,10 +144,10 @@ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNo
143144
.keyword("additionalItems").value(true).build());
144145
}
145146
}
146-
return errors.isEmpty() ? Collections.emptySet() : Collections.unmodifiableSet(errors);
147+
return errors.isEmpty() ? Collections.emptySet() : errors;
147148
}
148149

149-
private boolean doValidate(ExecutionContext executionContext, Set<ValidationMessage> errors, int i, JsonNode node,
150+
private boolean doValidate(ExecutionContext executionContext, SetView<ValidationMessage> errors, int i, JsonNode node,
150151
JsonNode rootNode, JsonNodePath instanceLocation) {
151152
boolean isAdditionalItem = false;
152153
JsonNodePath path = instanceLocation.append(i);
@@ -156,14 +157,14 @@ private boolean doValidate(ExecutionContext executionContext, Set<ValidationMess
156157
// schema)
157158
Set<ValidationMessage> results = this.schema.validate(executionContext, node, rootNode, path);
158159
if (!results.isEmpty()) {
159-
errors.addAll(results);
160+
errors.union(results);
160161
}
161162
} else if (this.tupleSchema != null) {
162163
if (i < this.tupleSchema.size()) {
163164
// validate against tuple schema
164165
Set<ValidationMessage> results = this.tupleSchema.get(i).validate(executionContext, node, rootNode, path);
165166
if (!results.isEmpty()) {
166-
errors.addAll(results);
167+
errors.union(results);
167168
}
168169
} else {
169170
if ((this.additionalItems != null && this.additionalItems) || this.additionalSchema != null) {
@@ -174,21 +175,21 @@ private boolean doValidate(ExecutionContext executionContext, Set<ValidationMess
174175
// validate against additional item schema
175176
Set<ValidationMessage> results = this.additionalSchema.validate(executionContext, node, rootNode, path);
176177
if (!results.isEmpty()) {
177-
errors.addAll(results);
178+
errors.union(results);
178179
}
179180
} else if (this.additionalItems != null) {
180181
if (this.additionalItems) {
181182
// evaluatedItems.add(path);
182183
} else {
183184
// no additional item allowed, return error
184-
errors.add(message().instanceNode(rootNode).instanceLocation(instanceLocation)
185+
errors.union(Collections.singleton(message().instanceNode(rootNode).instanceLocation(instanceLocation)
185186
.type("additionalItems")
186187
.messageKey("additionalItems")
187188
.evaluationPath(this.additionalItemsEvaluationPath)
188189
.schemaLocation(this.additionalItemsSchemaLocation)
189190
.schemaNode(this.additionalItemsSchemaNode)
190191
.locale(executionContext.getExecutionConfig().getLocale())
191-
.failFast(executionContext.isFailFast()).arguments(i).build());
192+
.failFast(executionContext.isFailFast()).arguments(i).build()));
192193
}
193194
}
194195
}

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import com.fasterxml.jackson.databind.JsonNode;
2020
import com.fasterxml.jackson.databind.node.ArrayNode;
2121
import com.networknt.schema.annotation.JsonNodeAnnotation;
22+
import com.networknt.schema.utils.SetView;
2223

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

6869
// ignores non-arrays
6970
if (node.isArray()) {
70-
Set<ValidationMessage> errors = new LinkedHashSet<>();
71-
// Collection<JsonNodePath> evaluatedItems = executionContext.getCollectorContext().getEvaluatedItems();
71+
SetView<ValidationMessage> errors = null;
7272
boolean evaluated = false;
7373
for (int i = this.prefixCount; i < node.size(); ++i) {
7474
JsonNodePath path = instanceLocation.append(i);
@@ -87,7 +87,10 @@ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNo
8787
if (results.isEmpty()) {
8888
// evaluatedItems.add(path);
8989
} else {
90-
errors.addAll(results);
90+
if (errors == null) {
91+
errors = new SetView<>();
92+
}
93+
errors.union(results);
9194
}
9295
evaluated = true;
9396
}
@@ -100,7 +103,7 @@ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNo
100103
.keyword(getKeyword()).value(true).build());
101104
}
102105
}
103-
return errors.isEmpty() ? Collections.emptySet() : Collections.unmodifiableSet(errors);
106+
return errors == null || errors.isEmpty() ? Collections.emptySet() : errors;
104107
} else {
105108
return Collections.emptySet();
106109
}

0 commit comments

Comments
 (0)