Skip to content

Commit c8d08d2

Browse files
committed
(maintaining unprocessed properties of schemas for any arbitary caller-specific usecase)
* adding Schema#unprocessedProperties, and populating it based on the remaining items of consumedKeys during loading * changing Schema#describePropertiesTo() to include the unprocessed props
1 parent f197797 commit c8d08d2

File tree

9 files changed

+144
-20
lines changed

9 files changed

+144
-20
lines changed

core/src/main/java/org/everit/json/schema/Schema.java

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package org.everit.json.schema;
22

3+
import static java.util.Collections.unmodifiableMap;
4+
35
import java.io.StringWriter;
6+
import java.util.HashMap;
7+
import java.util.Map;
48
import java.util.Objects;
59

610
import org.everit.json.schema.internal.JSONPrinter;
@@ -37,6 +41,8 @@ public abstract static class Builder<S extends Schema> {
3741

3842
private Boolean writeOnly = null;
3943

44+
private Map<String, Object> unprocessedProperties = new HashMap<>(0);
45+
4046
public Builder<S> title(String title) {
4147
this.title = title;
4248
return this;
@@ -77,6 +83,11 @@ public Builder<S> writeOnly(Boolean writeOnly) {
7783
return this;
7884
}
7985

86+
public Builder<S> unprocessedProperties(Map<String, Object> unprocessedProperties) {
87+
this.unprocessedProperties = unprocessedProperties;
88+
return this;
89+
}
90+
8091
public abstract S build();
8192

8293
}
@@ -97,6 +108,8 @@ public Builder<S> writeOnly(Boolean writeOnly) {
97108

98109
private final Boolean writeOnly;
99110

111+
private final Map<String, Object> unprocessedProperties;
112+
100113
/**
101114
* Constructor.
102115
*
@@ -112,6 +125,7 @@ protected Schema(Builder<?> builder) {
112125
this.nullable = builder.nullable;
113126
this.readOnly = builder.readOnly;
114127
this.writeOnly = builder.writeOnly;
128+
this.unprocessedProperties = new HashMap<>(builder.unprocessedProperties);
115129
}
116130

117131
/**
@@ -186,15 +200,16 @@ public boolean equals(Object o) {
186200
Objects.equals(id, schema.id) &&
187201
Objects.equals(nullable, schema.nullable) &&
188202
Objects.equals(readOnly, schema.readOnly) &&
189-
Objects.equals(writeOnly, schema.writeOnly);
203+
Objects.equals(writeOnly, schema.writeOnly) &&
204+
Objects.equals(unprocessedProperties, schema.unprocessedProperties);
190205
} else {
191206
return false;
192207
}
193208
}
194209

195210
@Override
196211
public int hashCode() {
197-
return Objects.hash(title, description, id, defaultValue, nullable, readOnly, writeOnly);
212+
return Objects.hash(title, description, id, defaultValue, nullable, readOnly, writeOnly, unprocessedProperties);
198213
}
199214

200215
public String getTitle() {
@@ -233,6 +248,14 @@ public Boolean isWriteOnly() {
233248
return writeOnly;
234249
}
235250

251+
/**
252+
* Returns the properties of the original schema JSON which aren't keywords of json schema
253+
* (therefore they weren't recognized during schema loading).
254+
*/
255+
public Map<String, Object> getUnprocessedProperties() {
256+
return unmodifiableMap(unprocessedProperties);
257+
}
258+
236259
/**
237260
* Describes the instance as a JSONObject to {@code writer}.
238261
* <p>
@@ -254,6 +277,9 @@ public void describeTo(JSONPrinter writer) {
254277
writer.ifPresent("readOnly", readOnly);
255278
writer.ifPresent("writeOnly", writeOnly);
256279
describePropertiesTo(writer);
280+
unprocessedProperties.forEach((key, val) -> {
281+
writer.key(key).value(val);
282+
});
257283
writer.endObject();
258284
}
259285

core/src/main/java/org/everit/json/schema/loader/SchemaExtractor.java

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.everit.json.schema.ObjectSchema;
2929
import org.everit.json.schema.Schema;
3030
import org.everit.json.schema.SchemaException;
31+
import org.everit.json.schema.StringSchema;
3132

3233
class ExtractionResult {
3334

@@ -129,6 +130,11 @@ NumberSchema.Builder buildNumberSchema() {
129130
return builder;
130131
}
131132

133+
StringSchema.Builder buildStringSchema() {
134+
PropertySnifferSchemaExtractor.STRING_SCHEMA_PROPS.forEach(this::keyConsumed);
135+
return new StringSchemaLoader(schemaJson.ls, config().formatValidators).load();
136+
}
137+
132138
abstract List<Schema.Builder<?>> extract();
133139
}
134140

@@ -177,16 +183,16 @@ class PropertySnifferSchemaExtractor extends AbstractSchemaExtractor {
177183
@Override List<Schema.Builder<?>> extract() {
178184
List<Schema.Builder<?>> builders = new ArrayList<>(1);
179185
if (schemaHasAnyOf(config().specVersion.arrayKeywords())) {
180-
builders.add(new ArraySchemaLoader(schemaJson.ls, config(), defaultLoader).load().requiresArray(false));
186+
builders.add(buildArraySchema().requiresArray(false));
181187
}
182188
if (schemaHasAnyOf(config().specVersion.objectKeywords())) {
183-
builders.add(new ObjectSchemaLoader(schemaJson.ls, config(), defaultLoader).load().requiresObject(false));
189+
builders.add(buildObjectSchema().requiresObject(false));
184190
}
185191
if (schemaHasAnyOf(NUMBER_SCHEMA_PROPS)) {
186192
builders.add(buildNumberSchema().requiresNumber(false));
187193
}
188194
if (schemaHasAnyOf(STRING_SCHEMA_PROPS)) {
189-
builders.add(new StringSchemaLoader(schemaJson.ls, config().formatValidators).load().requiresString(false));
195+
builders.add(buildStringSchema().requiresString(false));
190196
}
191197
if (config().specVersion.isAtLeast(DRAFT_7) && schemaHasAnyOf(CONDITIONAL_SCHEMA_KEYWORDS)) {
192198
builders.add(buildConditionalSchema());
@@ -232,8 +238,7 @@ private CombinedSchema.Builder buildAnyOfSchemaForMultipleTypes() {
232238
private Schema.Builder<?> loadForExplicitType(String typeString) {
233239
switch (typeString) {
234240
case "string":
235-
PropertySnifferSchemaExtractor.STRING_SCHEMA_PROPS.forEach(this::keyConsumed);
236-
return new StringSchemaLoader(schemaJson.ls, config().formatValidators).load();
241+
return buildStringSchema().requiresString(true);
237242
case "integer":
238243
return buildNumberSchema().requiresInteger(true);
239244
case "number":

core/src/main/java/org/everit/json/schema/loader/SchemaLoader.java

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,8 @@ private Schema.Builder loadSchemaBoolean(Boolean rawBoolean) {
338338
}
339339

340340
private Schema.Builder loadSchemaObject(JsonObject o) {
341-
Collection<Schema.Builder<?>> extractedSchemas = runSchemaExtractors(o);
341+
AdjacentSchemaExtractionState finalState = runSchemaExtractors(o);
342+
Collection<Schema.Builder<?>> extractedSchemas = finalState.extractedSchemaBuilders();
342343
Schema.Builder effectiveReturnedSchema;
343344
if (extractedSchemas.isEmpty()) {
344345
effectiveReturnedSchema = EmptySchema.builder();
@@ -351,15 +352,22 @@ private Schema.Builder loadSchemaObject(JsonObject o) {
351352
.collect(toList());
352353
effectiveReturnedSchema = CombinedSchema.allOf(built).isSynthetic(true);
353354
}
355+
Map<String, Object> unprocessed = finalState.projectedSchemaJson().toMap();
356+
if (config.nullableSupport && unprocessed.containsKey("nullable")) {
357+
unprocessed.remove("nullable");
358+
}
359+
effectiveReturnedSchema.unprocessedProperties(unprocessed);
354360
loadCommonSchemaProperties(effectiveReturnedSchema);
355361
return effectiveReturnedSchema;
356362
}
357363

358-
private Collection<Schema.Builder<?>> runSchemaExtractors(JsonObject o) {
364+
private AdjacentSchemaExtractionState runSchemaExtractors(JsonObject o) {
365+
AdjacentSchemaExtractionState state = new AdjacentSchemaExtractionState(o);
359366
if (o.containsKey("$ref")) {
360-
return new ReferenceSchemaExtractor(this).extract(o).extractedSchemas;
367+
ExtractionResult result = new ReferenceSchemaExtractor(this).extract(o);
368+
state = state.reduce(result);
369+
return state;
361370
}
362-
Collection<Schema.Builder<?>> extractedSchemas;
363371
List<SchemaExtractor> extractors = asList(
364372
new EnumSchemaExtractor(this),
365373
new CombinedSchemaLoader(this),
@@ -368,13 +376,11 @@ private Collection<Schema.Builder<?>> runSchemaExtractors(JsonObject o) {
368376
new TypeBasedSchemaExtractor(this),
369377
new PropertySnifferSchemaExtractor(this)
370378
);
371-
AdjacentSchemaExtractionState state = new AdjacentSchemaExtractionState(o);
372379
for (SchemaExtractor extractor : extractors) {
373380
ExtractionResult result = extractor.extract(state.projectedSchemaJson());
374381
state = state.reduce(result);
375382
}
376-
extractedSchemas = state.extractedSchemaBuilders();
377-
return extractedSchemas;
383+
return state;
378384
}
379385

380386
private void loadCommonSchemaProperties(Schema.Builder builder) {

core/src/test/java/org/everit/json/schema/NumberSchemaTest.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
import static org.junit.Assert.assertTrue;
2222
import static org.junit.Assert.fail;
2323

24-
import java.math.BigDecimal;
2524
import java.math.BigInteger;
2625
import java.util.concurrent.atomic.AtomicInteger;
2726
import java.util.concurrent.atomic.AtomicLong;
@@ -258,13 +257,13 @@ public void requiresInteger_nonNullable() {
258257
.input(JSONObject.NULL)
259258
.expect();
260259
}
261-
260+
262261
@Test
263262
public void accepts_bigInteger() {
264263
NumberSchema regularNumberSchema = NumberSchema.builder().build();
265264
NumberSchema requiresInteger = NumberSchema.builder().requiresInteger(true).build();
266-
267-
tryIntegerTypes(regularNumberSchema);
265+
266+
tryIntegerTypes(regularNumberSchema);
268267
tryIntegerTypes(requiresInteger);
269268
}
270269

core/src/test/java/org/everit/json/schema/ObjectSchemaTest.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -402,11 +402,20 @@ public void toStringNoExplicitType() {
402402
assertTrue(ObjectComparator.deepEquals(rawSchemaJson, new JSONObject(actual)));
403403
}
404404

405+
@Test
406+
public void toStringWithUnprocessedProps() {
407+
JSONObject rawSchemaJson = loader.readObj("tostring/objectschema-unprocessed.json");
408+
Schema schema = SchemaLoader.load(rawSchemaJson);
409+
String actual = schema.toString();
410+
assertTrue(ObjectComparator.deepEquals(rawSchemaJson, new JSONObject(actual)));
411+
}
412+
405413
@Test
406414
public void toStringNoAdditionalProperties() {
407415
JSONObject rawSchemaJson = loader.readObj("tostring/objectschema.json");
408416
rawSchemaJson.put("additionalProperties", false);
409-
String actual = SchemaLoader.load(rawSchemaJson).toString();
417+
Schema schema = SchemaLoader.load(rawSchemaJson);
418+
String actual = schema.toString();
410419
assertTrue(ObjectComparator.deepEquals(rawSchemaJson, new JSONObject(actual)));
411420
}
412421

core/src/test/java/org/everit/json/schema/loader/SchemaLoaderTest.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.everit.json.schema.loader;
22

33
import static java.util.Arrays.asList;
4+
import static java.util.Collections.emptyMap;
45
import static org.everit.json.schema.TestSupport.asStream;
56
import static org.everit.json.schema.TestSupport.loadAsV6;
67
import static org.everit.json.schema.TestSupport.loadAsV7;
@@ -57,6 +58,8 @@
5758
import org.junit.Ignore;
5859
import org.junit.Test;
5960

61+
import com.google.common.collect.ImmutableMap;
62+
6063
public class SchemaLoaderTest {
6164

6265
private static JSONObject ALL_SCHEMAS = ResourceLoader.DEFAULT.readObj("testschemas.json");
@@ -711,4 +714,23 @@ public void commonPropsGoIntoWrappingAllOf() {
711714
assertEquals("my description", actual.getDescription());
712715
assertNull(actual.getSubschemas().iterator().next().getId());
713716
}
717+
718+
@Test
719+
public void unprocessedPropertiesAreLoaded() {
720+
ObjectSchema actual = (ObjectSchema) SchemaLoader.load(get("schemaWithUnprocessedProperties"));
721+
722+
assertEquals(ImmutableMap.of(
723+
"unproc0", 1,
724+
"unproc1", "asdasd"
725+
), actual.getUnprocessedProperties());
726+
assertEquals(emptyMap(), actual.getPropertySchemas().get("prop").getUnprocessedProperties());
727+
assertEquals(ImmutableMap.of(
728+
"unproc4", true,
729+
"unproc5", JSONObject.NULL
730+
), actual.getPropertySchemas().get("prop2").getUnprocessedProperties());
731+
assertEquals(ImmutableMap.of(
732+
"unproc6", false
733+
), actual.getPropertySchemas().get("prop3").getUnprocessedProperties());
734+
}
735+
714736
}

core/src/test/resources/org/everit/jsonvalidator/testschemas.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -661,5 +661,23 @@
661661
"id": "http://id",
662662
"title": "my title",
663663
"description": "my description"
664+
},
665+
"schemaWithUnprocessedProperties": {
666+
"type": "object",
667+
"unproc0": 1,
668+
"unproc1": "asdasd",
669+
"properties": {
670+
"prop": {
671+
"type": "string"
672+
},
673+
"prop2": {
674+
"unproc4": true,
675+
"unproc5": null
676+
},
677+
"prop3": {
678+
"$ref": "#",
679+
"unproc6": false
680+
}
681+
}
664682
}
665683
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{
2+
"type": "object",
3+
"properties": {
4+
"numprop": {
5+
"type": "number"
6+
}
7+
},
8+
"minProperties": 3,
9+
"maxProperties": 4,
10+
"required": [
11+
"numprop"
12+
],
13+
"additionalProperties": {
14+
"type": "boolean"
15+
},
16+
"dependencies": {
17+
"prop1": [
18+
"prop2",
19+
"prop3"
20+
]
21+
},
22+
"patternProperties": {
23+
"pattern_.*": {
24+
"type": "boolean"
25+
}
26+
},
27+
"unproc0": true,
28+
"unproc1": false,
29+
"unproc2": null,
30+
"unproc3": 2,
31+
"unproc4": {
32+
"a": 1
33+
},
34+
"unproc5": [
35+
2,
36+
3,
37+
null
38+
]
39+
}

tests/vanilla/src/main/resources/org/everit/json/schema/issues/issue174/expectedException.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
},
1818
{
1919
"causingExceptions": [],
20-
"message": "#/prop: subject must not be valid against schema {}"
20+
"message": "#/prop: subject must not be valid against schema {\"const\":2}"
2121
}
2222
],
2323
"message": "#/prop: #: 0 subschemas matched instead of one"

0 commit comments

Comments
 (0)