diff --git a/doc/walkers.md b/doc/walkers.md index 84f8e152a..99e376679 100644 --- a/doc/walkers.md +++ b/doc/walkers.md @@ -1,6 +1,6 @@ ### JSON Schema Walkers -There can be use-cases where we need the capability to walk through the given JsonNode allowing functionality beyond validation like collecting information,handling cross cutting concerns like logging or instrumentation. JSON walkers were introduced to complement the validation functionality this library already provides. +There can be use-cases where we need the capability to walk through the given JsonNode allowing functionality beyond validation like collecting information,handling cross cutting concerns like logging or instrumentation, or applying default values. JSON walkers were introduced to complement the validation functionality this library already provides. Currently, walking is defined at the validator instance level for all the built-in keywords. @@ -237,4 +237,56 @@ Few important points to note about the flow. 5. Since we have a property listener defined, When we are walking through a property that has a "$ref" keyword which might have some more properties defined, Our property listener would be invoked for each of the property defined in the "$ref" schema. 6. As mentioned earlier anywhere during the "Walk Flow", we can return a WalkFlow.SKIP from onWalkStart method to stop the walk method of a particular "property schema" from being called. - Since the walk method will not be called any property or keyword listeners in the "property schema" will not be invoked. \ No newline at end of file + Since the walk method will not be called any property or keyword listeners in the "property schema" will not be invoked. + + +### Applying defaults + +In some use cases we may want to apply defaults while walking the schema. +To accomplish this, create an ApplyDefaultsStrategy when creating a SchemaValidatorsConfig. +The input object is changed in place, even if validation fails, or a fail-fast or some other exception is thrown. + +Here is the order of operations in walker. +1. apply defaults +1. run listeners +1. validate if shouldValidateSchema is true + +Suppose the JSON schema is +```json +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Schema with default values ", + "type": "object", + "properties": { + "intValue": { + "type": "integer", + "default": 15, + "minimum": 20 + } + }, + "required": ["intValue"] +} +``` + +A JSON file like +```json +{ +} +``` + +would normally fail validation as "intValue" is required. +But if we apply defaults while walking, then required validation passes, and the object is changed in place. + +```java + 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); + + JsonNode inputNode = objectMapper.readTree(getClass().getClassLoader().getResourceAsStream("data.json")); + ValidationResult result = jsonSchema.walk(inputNode, true); + assertThat(result.getValidationMessages(), Matchers.empty()); + assertEquals("{\"intValue\":15}", inputNode.toString()); + assertThat(result.getValidationMessages().stream().map(ValidationMessage::getMessage).collect(Collectors.toList()), + Matchers.containsInAnyOrder("$.intValue: must have a minimum value of 20.")); +``` diff --git a/pom.xml b/pom.xml index 4170c5d50..29202ba28 100644 --- a/pom.xml +++ b/pom.xml @@ -106,6 +106,12 @@ ${version.junit} test + + org.junit.jupiter + junit-jupiter-params + ${version.junit} + test + org.mockito mockito-core diff --git a/src/main/java/com/networknt/schema/ApplyDefaultsStrategy.java b/src/main/java/com/networknt/schema/ApplyDefaultsStrategy.java new file mode 100644 index 000000000..391a160a0 --- /dev/null +++ b/src/main/java/com/networknt/schema/ApplyDefaultsStrategy.java @@ -0,0 +1,43 @@ +package com.networknt.schema; + +public class ApplyDefaultsStrategy { + static final ApplyDefaultsStrategy EMPTY_APPLY_DEFAULTS_STRATEGY = new ApplyDefaultsStrategy(false, false, false); + + private final boolean applyPropertyDefaults; + private final boolean applyPropertyDefaultsIfNull; + private final boolean applyArrayDefaults; + + /** + * Specify which default values to apply. + * We can apply property defaults only if they are missing or if they are declared to be null in the input json, + * and we can apply array defaults if they are declared to be null in the input json. + * + *

Note that the walker changes the input object in place. + * If validation fails, the input object will be changed. + * + * @param applyPropertyDefaults if true then apply defaults inside json objects if the attribute is missing + * @param applyPropertyDefaultsIfNull if true then apply defaults inside json objects if the attribute is explicitly null + * @param applyArrayDefaults if true then apply defaults inside json arrays if the attribute is explicitly null + * @throws IllegalArgumentException if applyPropertyDefaults is false and applyPropertyDefaultsIfNull is true + */ + public ApplyDefaultsStrategy(boolean applyPropertyDefaults, boolean applyPropertyDefaultsIfNull, boolean applyArrayDefaults) { + if (!applyPropertyDefaults && applyPropertyDefaultsIfNull) { + throw new IllegalArgumentException(); + } + this.applyPropertyDefaults = applyPropertyDefaults; + this.applyPropertyDefaultsIfNull = applyPropertyDefaultsIfNull; + this.applyArrayDefaults = applyArrayDefaults; + } + + public boolean shouldApplyPropertyDefaults() { + return applyPropertyDefaults; + } + + public boolean shouldApplyPropertyDefaultsIfNull() { + return applyPropertyDefaultsIfNull; + } + + public boolean shouldApplyArrayDefaults() { + return applyArrayDefaults; + } +} diff --git a/src/main/java/com/networknt/schema/BaseJsonValidator.java b/src/main/java/com/networknt/schema/BaseJsonValidator.java index 3aa9a0b39..89283e386 100644 --- a/src/main/java/com/networknt/schema/BaseJsonValidator.java +++ b/src/main/java/com/networknt/schema/BaseJsonValidator.java @@ -38,16 +38,29 @@ public abstract class BaseJsonValidator implements JsonValidator { private ErrorMessageType errorMessageType; protected final boolean failFast; + protected final ApplyDefaultsStrategy applyDefaultsStrategy; public BaseJsonValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidatorTypeCode validatorType, ValidationContext validationContext) { this(schemaPath, schemaNode, parentSchema, validatorType, false, - validationContext.getConfig() != null && validationContext.getConfig().isFailFast()); + validationContext.getConfig() != null && validationContext.getConfig().isFailFast(), + validationContext.getConfig() != null ? validationContext.getConfig().getApplyDefaultsStrategy() : null); } + // TODO: can this be made package private? + @Deprecated // use the BaseJsonValidator below public BaseJsonValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidatorTypeCode validatorType, boolean suppressSubSchemaRetrieval, boolean failFast) { + this(schemaPath, schemaNode, parentSchema, validatorType, false, failFast, null); + } + public BaseJsonValidator(String schemaPath, + JsonNode schemaNode, + JsonSchema parentSchema, + ValidatorTypeCode validatorType, + boolean suppressSubSchemaRetrieval, + boolean failFast, + ApplyDefaultsStrategy applyDefaultsStrategy) { this.errorMessageType = validatorType; this.schemaPath = schemaPath; this.schemaNode = schemaNode; @@ -55,6 +68,7 @@ public BaseJsonValidator(String schemaPath, JsonNode schemaNode, JsonSchema pare this.validatorType = validatorType; this.suppressSubSchemaRetrieval = suppressSubSchemaRetrieval; this.failFast = failFast; + this.applyDefaultsStrategy = applyDefaultsStrategy != null ? applyDefaultsStrategy : ApplyDefaultsStrategy.EMPTY_APPLY_DEFAULTS_STRATEGY; } public String getSchemaPath() { diff --git a/src/main/java/com/networknt/schema/ItemsValidator.java b/src/main/java/com/networknt/schema/ItemsValidator.java index 69abc8bab..80c70426a 100644 --- a/src/main/java/com/networknt/schema/ItemsValidator.java +++ b/src/main/java/com/networknt/schema/ItemsValidator.java @@ -17,6 +17,7 @@ package com.networknt.schema; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; import com.networknt.schema.walk.DefaultItemWalkListenerRunner; import com.networknt.schema.walk.WalkListenerRunner; @@ -118,9 +119,21 @@ private void doValidate(Set errors, int i, JsonNode node, Jso @Override public Set walk(JsonNode node, JsonNode rootNode, String at, boolean shouldValidateSchema) { HashSet validationMessages = new LinkedHashSet(); - if (node != null && node.isArray()) { + if (node instanceof ArrayNode) { + ArrayNode arrayNode = (ArrayNode) node; + JsonNode defaultNode = null; + if (applyDefaultsStrategy.shouldApplyArrayDefaults() && schema != null) { + defaultNode = schema.getSchemaNode().get("default"); + if (defaultNode != null && defaultNode.isNull()) { + defaultNode = null; + } + } int i = 0; - for (JsonNode n : node) { + for (JsonNode n : arrayNode) { + if (n.isNull() && defaultNode != null) { + arrayNode.set(i, defaultNode); + n = defaultNode; + } doWalk(validationMessages, i, n, rootNode, at, shouldValidateSchema); i++; } diff --git a/src/main/java/com/networknt/schema/JsonSchema.java b/src/main/java/com/networknt/schema/JsonSchema.java index a4de67329..8d52d0647 100644 --- a/src/main/java/com/networknt/schema/JsonSchema.java +++ b/src/main/java/com/networknt/schema/JsonSchema.java @@ -19,14 +19,15 @@ import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URLDecoder; -import java.sql.Ref; import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import java.util.TreeMap; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -37,8 +38,6 @@ import com.networknt.schema.walk.JsonSchemaWalker; import com.networknt.schema.walk.WalkListenerRunner; -import javax.xml.validation.Schema; - /** * This is the core of json constraint implementation. It parses json constraint * file and generates JsonValidators. The class is thread safe, once it is @@ -80,7 +79,8 @@ public JsonSchema(ValidationContext validationContext, URI baseUri, JsonNode sch private JsonSchema(ValidationContext validationContext, String schemaPath, URI currentUri, JsonNode schemaNode, JsonSchema parent, boolean suppressSubSchemaRetrieval) { super(schemaPath, schemaNode, parent, null, suppressSubSchemaRetrieval, - validationContext.getConfig() != null && validationContext.getConfig().isFailFast()); + validationContext.getConfig() != null && validationContext.getConfig().isFailFast(), + validationContext.getConfig() != null ? validationContext.getConfig().getApplyDefaultsStrategy() : null); this.validationContext = validationContext; this.idKeyword = validationContext.getMetaSchema().getIdKeyword(); this.currentUri = this.combineCurrentUriWithIds(currentUri, schemaNode); @@ -208,7 +208,7 @@ private boolean nodeContainsRef(String ref, JsonNode node) { * used in {@link com.networknt.schema.walk.DefaultKeywordWalkListenerRunner} to derive the keyword. */ private Map read(JsonNode schemaNode) { - Map validators = new HashMap(); + Map validators = new TreeMap<>(VALIDATOR_SORT); if (schemaNode.isBoolean()) { if (schemaNode.booleanValue()) { final String customMessage = getCustomMessage(schemaNode, "true"); @@ -239,6 +239,20 @@ private Map read(JsonNode schemaNode) { return validators; } + /** + * A comparator that sorts validators, such such that 'properties' comes before 'required', + * so that we can apply default values before validating required. + */ + private static Comparator VALIDATOR_SORT = (lhs, rhs) -> { + if (lhs.endsWith("/properties")) { + return -1; + } + if (rhs.endsWith("/properties")) { + return 1; + } + return lhs.compareTo(rhs); + }; + private String getCustomMessage(JsonNode schemaNode, String pname) { final JsonSchema parentSchema = getParentSchema(); final JsonNode message = getMessageNode(schemaNode, parentSchema); @@ -320,7 +334,7 @@ protected ValidationResult validateAndCollect(JsonNode jsonNode, JsonNode rootNo SchemaValidatorsConfig config = validationContext.getConfig(); // Get the collector context from the thread local. CollectorContext collectorContext = getCollectorContext(); - // Valdiate. + // Validate. Set errors = validate(jsonNode, rootNode, at); // 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()) { @@ -374,7 +388,7 @@ public Set walk(JsonNode node, JsonNode rootNode, String at, JsonSchemaWalker jsonWalker = entry.getValue(); String schemaPathWithKeyword = entry.getKey(); try { - // Call all the pre-walk listeners. If atleast one of the pre walk listeners + // Call all the pre-walk listeners. If at least one of the pre walk listeners // returns SKIP, then skip the walk. if (keywordWalkListenerRunner.runPreWalkListeners(schemaPathWithKeyword, node, diff --git a/src/main/java/com/networknt/schema/PropertiesValidator.java b/src/main/java/com/networknt/schema/PropertiesValidator.java index b0e1154a0..69138a719 100644 --- a/src/main/java/com/networknt/schema/PropertiesValidator.java +++ b/src/main/java/com/networknt/schema/PropertiesValidator.java @@ -17,6 +17,7 @@ package com.networknt.schema; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.networknt.schema.walk.DefaultPropertyWalkListenerRunner; import com.networknt.schema.walk.WalkListenerRunner; import org.slf4j.Logger; @@ -52,7 +53,6 @@ public Set validate(JsonNode node, JsonNode rootNode, String for (Map.Entry entry : schemas.entrySet()) { JsonSchema propertySchema = entry.getValue(); JsonNode propertyNode = node.get(entry.getKey()); - if (propertyNode != null) { // check whether this is a complex validator. save the state boolean isComplex = state.isComplexValidator(); @@ -102,6 +102,9 @@ public Set validate(JsonNode node, JsonNode rootNode, String @Override public Set walk(JsonNode node, JsonNode rootNode, String at, boolean shouldValidateSchema) { HashSet validationMessages = new LinkedHashSet(); + if (applyDefaultsStrategy.shouldApplyPropertyDefaults()) { + applyPropertyDefaults((ObjectNode) node); + } if (shouldValidateSchema) { validationMessages.addAll(validate(node, rootNode, at)); } else { @@ -113,6 +116,21 @@ public Set walk(JsonNode node, JsonNode rootNode, String at, return validationMessages; } + private void applyPropertyDefaults(ObjectNode node) { + for (Map.Entry entry : schemas.entrySet()) { + JsonNode propertyNode = node.get(entry.getKey()); + + if (propertyNode == null || (applyDefaultsStrategy.shouldApplyPropertyDefaultsIfNull() && propertyNode.isNull())) { + JsonSchema propertySchema = entry.getValue(); + JsonNode defaultNode = propertySchema.getSchemaNode().get("default"); + if (defaultNode != null && !defaultNode.isNull()) { + // mutate the input json + node.set(entry.getKey(), defaultNode); + } + } + } + } + private void walkSchema(Map.Entry entry, JsonNode node, JsonNode rootNode, String at, boolean shouldValidateSchema, Set validationMessages, WalkListenerRunner propertyWalkListenerRunner) { JsonSchema propertySchema = entry.getValue(); diff --git a/src/main/java/com/networknt/schema/SchemaValidatorsConfig.java b/src/main/java/com/networknt/schema/SchemaValidatorsConfig.java index bb935ee35..b822e612f 100644 --- a/src/main/java/com/networknt/schema/SchemaValidatorsConfig.java +++ b/src/main/java/com/networknt/schema/SchemaValidatorsConfig.java @@ -37,6 +37,11 @@ public class SchemaValidatorsConfig { */ private boolean failFast; + /** + * When set to true, walker sets nodes that are missing or NullNode to the default value, if any, and mutate the input json. + */ + private ApplyDefaultsStrategy applyDefaultsStrategy; + /** * When set to true, use ECMA-262 compatible validator */ @@ -112,6 +117,14 @@ public boolean isFailFast() { return this.failFast; } + public void setApplyDefaultsStrategy(ApplyDefaultsStrategy applyDefaultsStrategy) { + this.applyDefaultsStrategy = applyDefaultsStrategy; + } + + public ApplyDefaultsStrategy getApplyDefaultsStrategy() { + return applyDefaultsStrategy; + } + public Map getUriMappings() { // return a copy of the mappings return new HashMap(uriMappings); diff --git a/src/test/java/com/networknt/schema/JsonWalkApplyDefaultsTest.java b/src/test/java/com/networknt/schema/JsonWalkApplyDefaultsTest.java new file mode 100644 index 000000000..4afe9a5c7 --- /dev/null +++ b/src/test/java/com/networknt/schema/JsonWalkApplyDefaultsTest.java @@ -0,0 +1,132 @@ +package com.networknt.schema; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; +import java.util.Set; +import java.util.stream.Collectors; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + + +class JsonWalkApplyDefaultsTest { + + @AfterEach + void cleanup() { + CollectorContext.getInstance().reset(); + } + + @ParameterizedTest + @ValueSource(booleans = { true, false}) + void testApplyDefaults3(boolean shouldValidateSchema) throws IOException { + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode inputNode = objectMapper.readTree(getClass().getClassLoader().getResourceAsStream("data/walk-data-default.json")); + JsonSchema jsonSchema = createSchema(new ApplyDefaultsStrategy(true, true, true)); + ValidationResult result = jsonSchema.walk(inputNode, shouldValidateSchema); + if (shouldValidateSchema) { + assertThat(result.getValidationMessages().stream().map(ValidationMessage::getMessage).collect(Collectors.toList()), + Matchers.containsInAnyOrder("$.outer.mixedObject.intValue_missingButError: string found, integer expected", + "$.outer.badArray[1]: integer found, string expected")); + } else { + assertThat(result.getValidationMessages(), Matchers.empty()); + } + // TODO: In Java 14 use text blocks + assertEquals( + objectMapper.readTree( + "{\"outer\":{\"mixedObject\":{\"intValue_present\":8,\"intValue_missing\":15,\"intValue_missing_notRequired\":25,\"intValue_null\":35,\"intValue_missingButError\":\"forty-five\"},\"goodArray\":[\"hello\",\"five\"],\"badArray\":[\"hello\",5],\"reference\":{\"stringValue_missing\":\"hello\"}}}"), + inputNode); + } + + @Test + void testApplyDefaults2() throws IOException { + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode inputNode = objectMapper.readTree(getClass().getClassLoader().getResourceAsStream("data/walk-data-default.json")); + JsonSchema jsonSchema = createSchema(new ApplyDefaultsStrategy(true, true, false)); + ValidationResult result = jsonSchema.walk(inputNode, true); + assertThat(result.getValidationMessages().stream().map(ValidationMessage::getMessage).collect(Collectors.toList()), + Matchers.containsInAnyOrder("$.outer.mixedObject.intValue_missingButError: string found, integer expected", + "$.outer.goodArray[1]: null found, string expected", + "$.outer.badArray[1]: null found, string expected")); + assertEquals( + objectMapper.readTree( + "{\"outer\":{\"mixedObject\":{\"intValue_present\":8,\"intValue_missing\":15,\"intValue_missing_notRequired\":25,\"intValue_null\":35,\"intValue_missingButError\":\"forty-five\"},\"goodArray\":[\"hello\",null],\"badArray\":[\"hello\",null],\"reference\":{\"stringValue_missing\":\"hello\"}}}"), + inputNode); + } + + @Test + void testApplyDefaults1() throws IOException { + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode inputNode = objectMapper.readTree(getClass().getClassLoader().getResourceAsStream("data/walk-data-default.json")); + JsonSchema jsonSchema = createSchema(new ApplyDefaultsStrategy(true, false, false)); + ValidationResult result = jsonSchema.walk(inputNode, true); + assertThat(result.getValidationMessages().stream().map(ValidationMessage::getMessage).collect(Collectors.toList()), + Matchers.containsInAnyOrder("$.outer.mixedObject.intValue_null: null found, integer expected", + "$.outer.mixedObject.intValue_missingButError: string found, integer expected", + "$.outer.goodArray[1]: null found, string expected", + "$.outer.badArray[1]: null found, string expected")); + assertEquals( + objectMapper.readTree( + "{\"outer\":{\"mixedObject\":{\"intValue_present\":8,\"intValue_missing\":15,\"intValue_missing_notRequired\":25,\"intValue_null\":null,\"intValue_missingButError\":\"forty-five\"},\"goodArray\":[\"hello\",null],\"badArray\":[\"hello\",null],\"reference\":{\"stringValue_missing\":\"hello\"}}}"), + inputNode); + } + + @ParameterizedTest + @ValueSource(strings = { "walkWithEmptyStrategy", "walkWithNoDefaults", "validateWithApplyAllDefaults"} ) + void testApplyDefaults0(String method) throws IOException { + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode inputNode = objectMapper.readTree(getClass().getClassLoader().getResourceAsStream("data/walk-data-default.json")); + JsonNode inputNodeOriginal = objectMapper.readTree(getClass().getClassLoader().getResourceAsStream("data/walk-data-default.json")); + Set validationMessages; + switch (method) { + case "walkWithEmptyStrategy": { + JsonSchema jsonSchema = createSchema(new ApplyDefaultsStrategy(false, false, false)); + validationMessages = jsonSchema.walk(inputNode, true).getValidationMessages(); + break; + } + case "walkWithNoDefaults": { + // same empty strategy, but tests for NullPointerException + JsonSchema jsonSchema = createSchema(null); + validationMessages = jsonSchema.walk(inputNode, true).getValidationMessages(); + break; + } + case "validateWithApplyAllDefaults": { + JsonSchema jsonSchema = createSchema(new ApplyDefaultsStrategy(true, true, true)); + validationMessages = jsonSchema.validate(inputNode); + break; + } + default: + throw new UnsupportedOperationException(); + } + assertThat(validationMessages.stream().map(ValidationMessage::getMessage).collect(Collectors.toList()), + Matchers.containsInAnyOrder("$.outer.mixedObject.intValue_missing: is missing but it is required", + "$.outer.mixedObject.intValue_missingButError: is missing but it is required", + "$.outer.mixedObject.intValue_null: null found, integer expected", + "$.outer.goodArray[1]: null found, string expected", + "$.outer.badArray[1]: null found, string expected", + "$.outer.reference.stringValue_missing: is missing but it is required")); + assertEquals(inputNodeOriginal, inputNode); + } + + @Test + void testIllegalArgumentException() { + try { + new ApplyDefaultsStrategy(false, true, false); + fail("expected IllegalArgumentException"); + } catch (IllegalArgumentException ignored) { + } + } + + private JsonSchema createSchema(ApplyDefaultsStrategy applyDefaultsStrategy) { + JsonSchemaFactory schemaFactory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4); + SchemaValidatorsConfig schemaValidatorsConfig = new SchemaValidatorsConfig(); + schemaValidatorsConfig.setApplyDefaultsStrategy(applyDefaultsStrategy); + return schemaFactory.getSchema(getClass().getClassLoader().getResourceAsStream("schema/walk-schema-default.json"), schemaValidatorsConfig); + } +} diff --git a/src/test/resources/data/walk-data-default.json b/src/test/resources/data/walk-data-default.json new file mode 100644 index 000000000..8a5245726 --- /dev/null +++ b/src/test/resources/data/walk-data-default.json @@ -0,0 +1,12 @@ +{ + "outer": { + "mixedObject": { + "intValue_present": 8, + "intValue_null": null + }, + "goodArray": ["hello", null], + "badArray": ["hello", null], + "reference": { + } + } +} \ No newline at end of file diff --git a/src/test/resources/schema/walk-schema-default.json b/src/test/resources/schema/walk-schema-default.json new file mode 100644 index 000000000..a3209136c --- /dev/null +++ b/src/test/resources/schema/walk-schema-default.json @@ -0,0 +1,92 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Schema with default values ", + + "definitions": { + "RandomObject": { + "type": "object", + "properties": { + "stringValue_missing": { + "description": "the test data does not have this attribute, so default should be applied if the ApplyDefaultsStrategy requires it", + "type": "string", + "default": "hello" + } + }, + "required": [ + "stringValue_missing" + ] + } + }, + + "type": "object", + "properties": { + "outer": { + "type": "object", + "properties": { + "mixedObject": { + "type": "object", + "properties": { + "intValue_present": { + "description": "the test data supplies a value for this attribute so the default is ignored", + "type": "integer", + "default": 5 + }, + "intValue_missing": { + "description": "the test data does not have this attribute, so default should be applied if the ApplyDefaultsStrategy requires it", + "type": "integer", + "default": 15 + }, + "intValue_missing_notRequired": { + "description": "the test data does not have this attribute, so default should be applied if the ApplyDefaultsStrategy requires it", + "type": "integer", + "default": 25 + }, + "intValue_null": { + "description": "the test data supplies the value null for this attribute so the default should be applied if the ApplyDefaultsStrategy requires it", + "type": "integer", + "default": 35 + }, + "intValue_missingButError": { + "description": "the test data does not have this attribute, so default should be applied if the ApplyDefaultsStrategy requires it, but the default is wrong so there should be an error", + "type": "integer", + "default": "forty-five" + } + }, + "additionalProperties": false, + "required": [ + "intValue_present", + "intValue_missing", + "intValue_null", + "intValue_missingButError" + ] + }, + + "goodArray": { + "type": "array", + "items": { + "description": "if an item in the array is null, then default value should be applied if the ApplyDefaultsStrategy requires it", + "type": "string", + "default": "five" + } + }, + + "badArray": { + "type": "array", + "items": { + "description": "if an item in the array is null, then default value should be applied if the ApplyDefaultsStrategy requires it, but the default is wrong so there should be an error", + "type": "string", + "default": 5 + } + }, + + "reference": { + "$ref": "#/definitions/RandomObject" + } + }, + "additionalProperties": false, + "required": ["mixedObject", "goodArray", "badArray", "reference"] + } + }, + "additionalProperties": false, + "required": ["outer"] +}