1 package com.networknt.schema; 2 3 import com.fasterxml.jackson.databind.JsonNode; 4 import com.fasterxml.jackson.databind.ObjectMapper; 5 import com.fasterxml.jackson.databind.node.ObjectNode; 6 import com.networknt.schema.walk.JsonSchemaWalkListener; 7 import com.networknt.schema.walk.WalkEvent; 8 import com.networknt.schema.walk.WalkFlow; 9 10 import org.junit.jupiter.api.BeforeEach; 11 import org.junit.jupiter.api.Test; 12 13 import java.io.IOException; 14 import java.io.InputStream; 15 import java.util.LinkedHashSet; 16 import java.util.Set; 17 import java.util.TreeSet; 18 19 import static org.junit.jupiter.api.Assertions.assertEquals; 20 21 public class JsonWalkTest { 22 23 private JsonSchema jsonSchema; 24 25 private JsonSchema jsonSchema1; 26 27 private static final String SAMPLE_WALK_COLLECTOR_TYPE = "sampleWalkCollectorType"; 28 29 private static final String CUSTOM_KEYWORD = "custom-keyword"; 30 31 @BeforeEach setup()32 public void setup() { 33 setupSchema(); 34 } 35 setupSchema()36 private void setupSchema() { 37 final JsonMetaSchema metaSchema = getJsonMetaSchema(); 38 // Create Schema. 39 SchemaValidatorsConfig schemaValidatorsConfig = new SchemaValidatorsConfig(); 40 schemaValidatorsConfig.addKeywordWalkListener(new AllKeywordListener()); 41 schemaValidatorsConfig.addKeywordWalkListener(ValidatorTypeCode.REF.getValue(), new RefKeywordListener()); 42 schemaValidatorsConfig.addKeywordWalkListener(ValidatorTypeCode.PROPERTIES.getValue(), 43 new PropertiesKeywordListener()); 44 final JsonSchemaFactory schemaFactory = JsonSchemaFactory 45 .builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909)).metaSchema(metaSchema) 46 .build(); 47 this.jsonSchema = schemaFactory.getSchema(getSchema(), schemaValidatorsConfig); 48 // Create another Schema. 49 SchemaValidatorsConfig schemaValidatorsConfig1 = new SchemaValidatorsConfig(); 50 schemaValidatorsConfig1.addKeywordWalkListener(ValidatorTypeCode.REF.getValue(), new RefKeywordListener()); 51 schemaValidatorsConfig1.addKeywordWalkListener(ValidatorTypeCode.PROPERTIES.getValue(), 52 new PropertiesKeywordListener()); 53 this.jsonSchema1 = schemaFactory.getSchema(getSchema(), schemaValidatorsConfig1); 54 } 55 getJsonMetaSchema()56 private JsonMetaSchema getJsonMetaSchema() { 57 return JsonMetaSchema.builder( 58 "https://github.com/networknt/json-schema-validator/tests/schemas/example01", JsonMetaSchema.getV201909()) 59 .keyword(new CustomKeyword()).build(); 60 } 61 62 @Test testWalk()63 public void testWalk() throws IOException { 64 ObjectMapper objectMapper = new ObjectMapper(); 65 ValidationResult result = jsonSchema.walk( 66 objectMapper.readTree(getClass().getClassLoader().getResourceAsStream("data/walk-data.json")), false); 67 JsonNode collectedNode = (JsonNode) result.getCollectorContext().get(SAMPLE_WALK_COLLECTOR_TYPE); 68 assertEquals(collectedNode, (objectMapper.readTree("{" + 69 " \"PROPERTY1\": \"sample1\"," 70 + " \"PROPERTY2\": \"sample2\"," 71 + " \"property3\": {" 72 + " \"street_address\":\"test-address\"," 73 + " \"phone_number\": {" 74 + " \"country-code\": \"091\"," 75 + " \"number\": \"123456789\"" 76 + " }" 77 + " }" 78 + "}"))); 79 } 80 81 @Test testWalkWithDifferentListeners()82 public void testWalkWithDifferentListeners() throws IOException { 83 ObjectMapper objectMapper = new ObjectMapper(); 84 // This instance of schema contains all listeners. 85 ValidationResult result = jsonSchema.walk( 86 objectMapper.readTree(getClass().getClassLoader().getResourceAsStream("data/walk-data.json")), false); 87 JsonNode collectedNode = (JsonNode) result.getCollectorContext().get(SAMPLE_WALK_COLLECTOR_TYPE); 88 assertEquals(collectedNode, (objectMapper.readTree("{" + 89 " \"PROPERTY1\": \"sample1\"," 90 + " \"PROPERTY2\": \"sample2\"," 91 + " \"property3\": {" 92 + " \"street_address\":\"test-address\"," 93 + " \"phone_number\": {" 94 + " \"country-code\": \"091\"," 95 + " \"number\": \"123456789\"" 96 + " }" 97 + " }" 98 + "}"))); 99 // This instance of schema contains one listener removed. 100 result = jsonSchema1.walk(objectMapper.readTree(getClass().getClassLoader().getResourceAsStream("data/walk-data.json")), false); 101 collectedNode = (JsonNode) result.getExecutionContext().getCollectorContext().get(SAMPLE_WALK_COLLECTOR_TYPE); 102 assertEquals(collectedNode, (objectMapper.readTree("{" 103 + " \"property3\": {" 104 + " \"street_address\":\"test-address\"," 105 + " \"phone_number\": {" 106 + " \"country-code\": \"091\"," 107 + " \"number\": \"123456789\"" 108 + " }" 109 + " }" 110 + "}"))); 111 } 112 getSchema()113 private InputStream getSchema() { 114 return getClass().getClassLoader().getResourceAsStream("schema/walk-schema.json"); 115 } 116 117 /** 118 * Our own custom keyword. 119 */ 120 private static class CustomKeyword implements Keyword { 121 @Override getValue()122 public String getValue() { 123 return "custom-keyword"; 124 } 125 126 @Override newValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext)127 public JsonValidator newValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, 128 JsonSchema parentSchema, ValidationContext validationContext) throws JsonSchemaException { 129 if (schemaNode != null && schemaNode.isArray()) { 130 return new CustomValidator(schemaLocation, evaluationPath, schemaNode); 131 } 132 return null; 133 } 134 135 /** 136 * We will be collecting information/data by adding the data in the form of 137 * collectors into collector context object while we are validating this node. 138 * This will be helpful in cases where we don't want to revisit the entire JSON 139 * document again just for gathering this kind of information. 140 */ 141 private static class CustomValidator extends AbstractJsonValidator { 142 CustomValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode)143 public CustomValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode) { 144 super(schemaLocation, evaluationPath, new CustomKeyword(), schemaNode); 145 } 146 147 @Override validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation)148 public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) { 149 return new TreeSet<ValidationMessage>(); 150 } 151 152 @Override walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation, boolean shouldValidateSchema)153 public Set<ValidationMessage> walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, 154 JsonNodePath instanceLocation, boolean shouldValidateSchema) { 155 return new LinkedHashSet<ValidationMessage>(); 156 } 157 } 158 } 159 160 private static class AllKeywordListener implements JsonSchemaWalkListener { 161 @Override onWalkStart(WalkEvent keywordWalkEvent)162 public WalkFlow onWalkStart(WalkEvent keywordWalkEvent) { 163 ObjectMapper mapper = new ObjectMapper(); 164 String keyWordName = keywordWalkEvent.getKeyword(); 165 JsonNode schemaNode = keywordWalkEvent.getSchema().getSchemaNode(); 166 CollectorContext collectorContext = keywordWalkEvent.getExecutionContext().getCollectorContext(); 167 if (collectorContext.get(SAMPLE_WALK_COLLECTOR_TYPE) == null) { 168 collectorContext.add(SAMPLE_WALK_COLLECTOR_TYPE, mapper.createObjectNode()); 169 } 170 if (keyWordName.equals(CUSTOM_KEYWORD) && schemaNode.get(CUSTOM_KEYWORD).isArray()) { 171 ObjectNode objectNode = (ObjectNode) collectorContext.get(SAMPLE_WALK_COLLECTOR_TYPE); 172 objectNode.put(keywordWalkEvent.getSchema().getSchemaNode().get("title").textValue().toUpperCase(), 173 keywordWalkEvent.getInstanceNode().textValue()); 174 } 175 return WalkFlow.CONTINUE; 176 } 177 178 @Override onWalkEnd(WalkEvent keywordWalkEvent, Set<ValidationMessage> validationMessages)179 public void onWalkEnd(WalkEvent keywordWalkEvent, Set<ValidationMessage> validationMessages) { 180 181 } 182 } 183 184 private static class RefKeywordListener implements JsonSchemaWalkListener { 185 186 @Override onWalkStart(WalkEvent keywordWalkEvent)187 public WalkFlow onWalkStart(WalkEvent keywordWalkEvent) { 188 ObjectMapper mapper = new ObjectMapper(); 189 CollectorContext collectorContext = keywordWalkEvent.getExecutionContext().getCollectorContext(); 190 if (collectorContext.get(SAMPLE_WALK_COLLECTOR_TYPE) == null) { 191 collectorContext.add(SAMPLE_WALK_COLLECTOR_TYPE, mapper.createObjectNode()); 192 } 193 ObjectNode objectNode = (ObjectNode) collectorContext.get(SAMPLE_WALK_COLLECTOR_TYPE); 194 objectNode.set(keywordWalkEvent.getSchema().getSchemaNode().get("title").textValue().toLowerCase(), 195 keywordWalkEvent.getInstanceNode()); 196 return WalkFlow.SKIP; 197 } 198 199 @Override onWalkEnd(WalkEvent keywordWalkEvent, Set<ValidationMessage> validationMessages)200 public void onWalkEnd(WalkEvent keywordWalkEvent, Set<ValidationMessage> validationMessages) { 201 202 } 203 } 204 205 private static class PropertiesKeywordListener implements JsonSchemaWalkListener { 206 207 @Override onWalkStart(WalkEvent keywordWalkEvent)208 public WalkFlow onWalkStart(WalkEvent keywordWalkEvent) { 209 JsonNode schemaNode = keywordWalkEvent.getSchema().getSchemaNode(); 210 if (schemaNode.get("title").textValue().equals("Property3")) { 211 return WalkFlow.SKIP; 212 } 213 return WalkFlow.CONTINUE; 214 } 215 216 @Override onWalkEnd(WalkEvent keywordWalkEvent, Set<ValidationMessage> validationMessages)217 public void onWalkEnd(WalkEvent keywordWalkEvent, Set<ValidationMessage> validationMessages) { 218 219 } 220 } 221 222 } 223