Skip to content

Commit bdfb117

Browse files
authored
Merge pull request #53 from joblift/optimized-oneof
Optimization for OneOf
2 parents 3d52d67 + 96d26bc commit bdfb117

File tree

2 files changed

+119
-19
lines changed

2 files changed

+119
-19
lines changed

Diff for: src/main/java/com/networknt/schema/OneOfValidator.java

+103-8
Original file line numberDiff line numberDiff line change
@@ -16,28 +16,113 @@
1616

1717
package com.networknt.schema;
1818

19-
import com.fasterxml.jackson.databind.JsonNode;
20-
21-
import org.slf4j.Logger;
22-
import org.slf4j.LoggerFactory;
23-
2419
import java.util.ArrayList;
2520
import java.util.Collections;
21+
import java.util.HashMap;
2622
import java.util.Iterator;
2723
import java.util.LinkedHashSet;
2824
import java.util.List;
25+
import java.util.Map;
2926
import java.util.Set;
3027

28+
import org.slf4j.Logger;
29+
import org.slf4j.LoggerFactory;
30+
31+
import com.fasterxml.jackson.databind.JsonNode;
32+
3133
public class OneOfValidator extends BaseJsonValidator implements JsonValidator {
3234
private static final Logger logger = LoggerFactory.getLogger(RequiredValidator.class);
3335

34-
private List<JsonSchema> schemas = new ArrayList<JsonSchema>();
36+
private List<ShortcutValidator> schemas = new ArrayList<ShortcutValidator>();
37+
38+
private static class ShortcutValidator {
39+
private final JsonSchema schema;
40+
private final Map<String, String> constants;
41+
42+
ShortcutValidator(JsonNode schemaNode, JsonSchema parentSchema,
43+
ValidationContext validationContext, JsonSchema schema) {
44+
JsonNode refNode = schemaNode.get(ValidatorTypeCode.REF.getValue());
45+
JsonSchema resolvedRefSchema = refNode != null && refNode.isTextual() ? RefValidator.getRefSchema(parentSchema, validationContext,refNode.textValue()) : null;
46+
this.constants = extractConstants(schemaNode, resolvedRefSchema);
47+
this.schema = schema;
48+
}
49+
50+
private Map<String, String> extractConstants(JsonNode schemaNode, JsonSchema resolvedRefSchema) {
51+
Map<String, String> refMap = resolvedRefSchema != null ? extractConstants(resolvedRefSchema.getSchemaNode()) : Collections.<String,String>emptyMap();
52+
Map<String, String> schemaMap = extractConstants(schemaNode);
53+
if (refMap.isEmpty() ) {
54+
return schemaMap;
55+
}
56+
if (schemaMap.isEmpty()) {
57+
return refMap;
58+
}
59+
Map<String, String> joined = new HashMap<String, String>();
60+
joined.putAll(schemaMap);
61+
joined.putAll(refMap);
62+
return joined;
63+
}
64+
65+
private Map<String, String> extractConstants(JsonNode schemaNode) {
66+
Map<String, String> result = new HashMap<String, String>();
67+
if (!schemaNode.isObject()) {
68+
return result;
69+
}
70+
71+
JsonNode propertiesNode = schemaNode.get("properties");
72+
if (propertiesNode == null || !propertiesNode.isObject()) {
73+
return result;
74+
}
75+
Iterator<String> fit = propertiesNode.fieldNames();
76+
while (fit.hasNext()) {
77+
String fieldName = fit.next();
78+
JsonNode jsonNode = propertiesNode.get(fieldName);
79+
String constantFieldValue = getConstantFieldValue(jsonNode);
80+
if (constantFieldValue != null && !constantFieldValue.isEmpty()) {
81+
result.put(fieldName, constantFieldValue);
82+
}
83+
}
84+
return result;
85+
}
86+
private String getConstantFieldValue(JsonNode jsonNode) {
87+
if (jsonNode == null || !jsonNode.isObject() || !jsonNode.has("enum")) {
88+
return null;
89+
}
90+
JsonNode enumNode = jsonNode.get("enum");
91+
if (enumNode == null || !enumNode.isArray() ) {
92+
return null;
93+
}
94+
if (enumNode.size() != 1) {
95+
return null;
96+
}
97+
JsonNode valueNode = enumNode.get(0);
98+
if (valueNode == null || !valueNode.isTextual()) {
99+
return null;
100+
}
101+
return valueNode.textValue();
102+
}
103+
104+
public boolean allConstantsMatch(JsonNode node) {
105+
for (Map.Entry<String, String> e: constants.entrySet()) {
106+
JsonNode valueNode = node.get(e.getKey());
107+
if (valueNode != null && valueNode.isTextual()) {
108+
boolean match = e.getValue().equals(valueNode.textValue());
109+
if (!match) {
110+
return false;
111+
}
112+
}
113+
}
114+
return true;
115+
}
116+
117+
}
35118

36119
public OneOfValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
37120
super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.ONE_OF, validationContext);
38121
int size = schemaNode.size();
39122
for (int i = 0; i < size; i++) {
40-
schemas.add(new JsonSchema(validationContext, getValidatorType().getValue(), schemaNode.get(i), parentSchema));
123+
JsonNode childNode = schemaNode.get(i);
124+
JsonSchema childSchema = new JsonSchema(validationContext, getValidatorType().getValue(), childNode, parentSchema);
125+
schemas.add(new ShortcutValidator(childNode, parentSchema, validationContext, childSchema));
41126
}
42127

43128
parseErrorCode(getValidatorType().getErrorCodeKey());
@@ -49,7 +134,13 @@ public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String
49134
int numberOfValidSchema = 0;
50135
Set<ValidationMessage> errors = new LinkedHashSet<ValidationMessage>();
51136

52-
for (JsonSchema schema : schemas) {
137+
for (ShortcutValidator validator : schemas) {
138+
if (!validator.allConstantsMatch(node)) {
139+
// take a shortcut: if there is any constant that does not match,
140+
// we can bail out
141+
continue;
142+
}
143+
JsonSchema schema = validator.schema;
53144
Set<ValidationMessage> schemaErrors = schema.validate(node, rootNode, at);
54145
if (schemaErrors.isEmpty()) {
55146
numberOfValidSchema++;
@@ -70,6 +161,10 @@ public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String
70161
if (ValidatorTypeCode.ADDITIONAL_PROPERTIES.getValue().equals(msg.getType())) {
71162
it.remove();
72163
}
164+
if (errors.isEmpty()) {
165+
// ensure there is always an error reported if number of valid schemas is 0
166+
errors.add(buildValidationMessage(at, ""));
167+
}
73168
}
74169
}
75170
if (numberOfValidSchema > 1) {

Diff for: src/main/java/com/networknt/schema/RefValidator.java

+16-11
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,21 @@ public class RefValidator extends BaseJsonValidator implements JsonValidator {
3434

3535
protected JsonSchema schema;
3636

37-
private final String REF_DOMAIN = "/";
38-
private final String REF_CURRENT = "#";
39-
private final String REF_RELATIVE = "../";
37+
private static final String REF_DOMAIN = "/";
38+
private static final String REF_CURRENT = "#";
39+
private static final String REF_RELATIVE = "../";
4040

4141
public RefValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
4242

4343
super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.REF, validationContext);
4444
String refValue = schemaNode.asText();
45+
schema = getRefSchema(parentSchema, validationContext, refValue);
46+
if (schema == null) {
47+
throw new JsonSchemaException(ValidationMessage.of(ValidatorTypeCode.REF.getValue(), CustomErrorMessageType.of("internal.unresolvedRef", new MessageFormat("{0}: Reference {1} cannot be resolved")), schemaPath, refValue));
48+
}
49+
}
50+
51+
static JsonSchema getRefSchema(JsonSchema parentSchema, ValidationContext validationContext, String refValue) {
4552
if (!refValue.startsWith(REF_CURRENT)) {
4653
// handle remote ref
4754
String schemaUrl = refValue;
@@ -61,29 +68,27 @@ public RefValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSch
6168
parentSchema = validationContext.getJsonSchemaFactory().getSchema(is);
6269
}
6370
if (index < 0) {
64-
schema = parentSchema.findAncestor();
71+
return parentSchema.findAncestor();
6572
} else {
6673
refValue = refValue.substring(index);
6774
}
6875
}
6976
if (refValue.equals(REF_CURRENT)) {
70-
schema = parentSchema.findAncestor();
77+
return parentSchema.findAncestor();
7178
} else {
7279
JsonNode node = parentSchema.getRefSchemaNode(refValue);
7380
if (node != null) {
74-
schema = new JsonSchema(validationContext, refValue, node, parentSchema);
81+
return new JsonSchema(validationContext, refValue, node, parentSchema);
7582
}
7683
}
77-
if (schema == null) {
78-
throw new JsonSchemaException(ValidationMessage.of(ValidatorTypeCode.REF.getValue(), CustomErrorMessageType.of("internal.unresolvedRef", new MessageFormat("{0}: Reference {1} cannot be resolved")), schemaPath, refValue));
79-
}
84+
return null;
8085
}
8186

82-
private boolean isRelativePath(String schemaUrl) {
87+
private static boolean isRelativePath(String schemaUrl) {
8388
return !schemaUrl.startsWith("http");
8489
}
8590

86-
private String obtainAbsolutePath(JsonSchema parentSchema, String schemaUrl) {
91+
private static String obtainAbsolutePath(JsonSchema parentSchema, String schemaUrl) {
8792
String baseSchemaUrl = parentSchema.findAncestor().getSchemaNode().get("id").textValue();
8893
int index = baseSchemaUrl.lastIndexOf("/");
8994
baseSchemaUrl = baseSchemaUrl.substring(0, index);

0 commit comments

Comments
 (0)