1*78c4dd6aSAndroid Build Coastguard Worker# Obtaining YAML Line Numbers 2*78c4dd6aSAndroid Build Coastguard Worker 3*78c4dd6aSAndroid Build Coastguard Worker## Scenario 1 - finding YAML line numbers from the JSON tree 4*78c4dd6aSAndroid Build Coastguard Worker 5*78c4dd6aSAndroid Build Coastguard WorkerA great feature of json-schema-validator is it's ability to validate YAML documents against a JSON Scheme. The manner in which this is done though, by pre-processing the YAML into a tree of [JsonNode](https://fasterxml.github.io/jackson-databind/javadoc/2.10/com/fasterxml/jackson/databind/JsonNode.html) objects, breaks the connection back to the original YAML source file. Very commonly, once the YAML has been validated against the schema, there may be additional processing and checking for semantic or content errors or inconsistency in the JSON tree. From an end user point of view, the ideal is to report such errors using line and column references back to the original YAML, but this information is not readily available from the processed JSON tree. 6*78c4dd6aSAndroid Build Coastguard Worker 7*78c4dd6aSAndroid Build Coastguard Worker### Scenario 1, solution part 1 - capturing line details during initial parsing 8*78c4dd6aSAndroid Build Coastguard Worker 9*78c4dd6aSAndroid Build Coastguard WorkerOne solution is to use a custom [JsonNodeFactory](https://fasterxml.github.io/jackson-databind/javadoc/2.10/com/fasterxml/jackson/databind/node/JsonNodeFactory.html) that returns custom JsonNode objects which are created during initial parsing, and which record the original YAML locations that were being parsed at the time they were created. The example below shows this 10*78c4dd6aSAndroid Build Coastguard Worker 11*78c4dd6aSAndroid Build Coastguard Worker```java 12*78c4dd6aSAndroid Build Coastguard Worker public static class MyNodeFactory extends JsonNodeFactory 13*78c4dd6aSAndroid Build Coastguard Worker { 14*78c4dd6aSAndroid Build Coastguard Worker YAMLParser yp; 15*78c4dd6aSAndroid Build Coastguard Worker 16*78c4dd6aSAndroid Build Coastguard Worker public MyNodeFactory(YAMLParser yp) 17*78c4dd6aSAndroid Build Coastguard Worker { 18*78c4dd6aSAndroid Build Coastguard Worker super(); 19*78c4dd6aSAndroid Build Coastguard Worker this.yp = yp; 20*78c4dd6aSAndroid Build Coastguard Worker } 21*78c4dd6aSAndroid Build Coastguard Worker 22*78c4dd6aSAndroid Build Coastguard Worker public ArrayNode arrayNode() 23*78c4dd6aSAndroid Build Coastguard Worker { 24*78c4dd6aSAndroid Build Coastguard Worker return new MyArrayNode(this, yp.getTokenLocation(), yp.getCurrentLocation()); 25*78c4dd6aSAndroid Build Coastguard Worker } 26*78c4dd6aSAndroid Build Coastguard Worker 27*78c4dd6aSAndroid Build Coastguard Worker public BooleanNode booleanNode(boolean v) 28*78c4dd6aSAndroid Build Coastguard Worker { 29*78c4dd6aSAndroid Build Coastguard Worker return new MyBooleanNode(v, yp.getTokenLocation(), yp.getCurrentLocation()); 30*78c4dd6aSAndroid Build Coastguard Worker } 31*78c4dd6aSAndroid Build Coastguard Worker 32*78c4dd6aSAndroid Build Coastguard Worker public NumericNode numberNode(int v) 33*78c4dd6aSAndroid Build Coastguard Worker { 34*78c4dd6aSAndroid Build Coastguard Worker return new MyIntNode(v, yp.getTokenLocation(), yp.getCurrentLocation()); 35*78c4dd6aSAndroid Build Coastguard Worker } 36*78c4dd6aSAndroid Build Coastguard Worker 37*78c4dd6aSAndroid Build Coastguard Worker public NullNode nullNode() 38*78c4dd6aSAndroid Build Coastguard Worker { 39*78c4dd6aSAndroid Build Coastguard Worker return new MyNullNode(yp.getTokenLocation(), yp.getCurrentLocation()); 40*78c4dd6aSAndroid Build Coastguard Worker } 41*78c4dd6aSAndroid Build Coastguard Worker 42*78c4dd6aSAndroid Build Coastguard Worker public ObjectNode objectNode() 43*78c4dd6aSAndroid Build Coastguard Worker { 44*78c4dd6aSAndroid Build Coastguard Worker return new MyObjectNode(this, yp.getTokenLocation(), yp.getCurrentLocation()); 45*78c4dd6aSAndroid Build Coastguard Worker } 46*78c4dd6aSAndroid Build Coastguard Worker 47*78c4dd6aSAndroid Build Coastguard Worker public TextNode textNode(String text) 48*78c4dd6aSAndroid Build Coastguard Worker { 49*78c4dd6aSAndroid Build Coastguard Worker return (text != null) ? new MyTextNode(text, yp.getTokenLocation(), yp.getCurrentLocation()) : null; 50*78c4dd6aSAndroid Build Coastguard Worker } 51*78c4dd6aSAndroid Build Coastguard Worker } 52*78c4dd6aSAndroid Build Coastguard Worker``` 53*78c4dd6aSAndroid Build Coastguard Worker 54*78c4dd6aSAndroid Build Coastguard WorkerThe example above includes a basic, but usable subset of all possible JsonNode types - if your YAML needs them, than you should also consider the others i.e. `byte`, `byte[]`, `raw`, `short`, `long`, `float`, `double`, `BigInteger`, `BigDecimal` 55*78c4dd6aSAndroid Build Coastguard Worker 56*78c4dd6aSAndroid Build Coastguard WorkerThere are some important other things to note from the example: 57*78c4dd6aSAndroid Build Coastguard Worker 58*78c4dd6aSAndroid Build Coastguard Worker* Even in a reduced set, `ObjectNode` and `NullNode` should be included 59*78c4dd6aSAndroid Build Coastguard Worker* The current return for methods that receive a null parameter value seems to be null rather than `NullNode` (based on inspecting the underlying `valueOf()` methods in the various `JsonNode` sub classes). Hence the implementation of the `textNode()` method above. 60*78c4dd6aSAndroid Build Coastguard Worker 61*78c4dd6aSAndroid Build Coastguard WorkerThe actual work here is really being done by the YAMLParser - it holds the location of the token being parsed, and the current location in the file. The first of these gives us a line and column number we can use to flag where an error or problem was found, and the second (if needed) can let us calculate a span to the end of the error e.g. if we wanted to highlight or underline the text in error. 62*78c4dd6aSAndroid Build Coastguard Worker 63*78c4dd6aSAndroid Build Coastguard Worker### Scenario 1, solution part 2 - augmented `JsonNode` subclassess 64*78c4dd6aSAndroid Build Coastguard Worker 65*78c4dd6aSAndroid Build Coastguard WorkerWe can be as simple or fancy as we like in the `JsonNode` subclassses, but basically we need 2 pieces of information from them: 66*78c4dd6aSAndroid Build Coastguard Worker 67*78c4dd6aSAndroid Build Coastguard Worker* An interface so when we are post processing the JSON tree, we can recognize nodes that retain line number information 68*78c4dd6aSAndroid Build Coastguard Worker* An interface that lets us extract the relevant location information 69*78c4dd6aSAndroid Build Coastguard Worker 70*78c4dd6aSAndroid Build Coastguard WorkerThose could be the same thing of course, but in our case we separated them as shown in the following example 71*78c4dd6aSAndroid Build Coastguard Worker 72*78c4dd6aSAndroid Build Coastguard Worker```java 73*78c4dd6aSAndroid Build Coastguard Worker public interface LocationProvider 74*78c4dd6aSAndroid Build Coastguard Worker { 75*78c4dd6aSAndroid Build Coastguard Worker LocationDetails getLocationDetails(); 76*78c4dd6aSAndroid Build Coastguard Worker } 77*78c4dd6aSAndroid Build Coastguard Worker 78*78c4dd6aSAndroid Build Coastguard Worker public interface LocationDetails 79*78c4dd6aSAndroid Build Coastguard Worker { 80*78c4dd6aSAndroid Build Coastguard Worker default int getLineNumber() { return 1; } 81*78c4dd6aSAndroid Build Coastguard Worker default int getColumnNumber() { return 1; } 82*78c4dd6aSAndroid Build Coastguard Worker default String getFilename() { return ""; } 83*78c4dd6aSAndroid Build Coastguard Worker } 84*78c4dd6aSAndroid Build Coastguard Worker 85*78c4dd6aSAndroid Build Coastguard Worker public static class LocationDetailsImpl implements LocationDetails 86*78c4dd6aSAndroid Build Coastguard Worker { 87*78c4dd6aSAndroid Build Coastguard Worker final JsonLocation currentLocation; 88*78c4dd6aSAndroid Build Coastguard Worker final JsonLocation tokenLocation; 89*78c4dd6aSAndroid Build Coastguard Worker 90*78c4dd6aSAndroid Build Coastguard Worker public LocationDetailsImpl(JsonLocation tokenLocation, JsonLocation currentLocation) 91*78c4dd6aSAndroid Build Coastguard Worker { 92*78c4dd6aSAndroid Build Coastguard Worker this.tokenLocation = tokenLocation; 93*78c4dd6aSAndroid Build Coastguard Worker this.currentLocation = currentLocation; 94*78c4dd6aSAndroid Build Coastguard Worker } 95*78c4dd6aSAndroid Build Coastguard Worker 96*78c4dd6aSAndroid Build Coastguard Worker @Override 97*78c4dd6aSAndroid Build Coastguard Worker public int getLineNumber() { return (tokenLocation != null) ? tokenLocation.getLineNr() : 1; }; 98*78c4dd6aSAndroid Build Coastguard Worker @Override 99*78c4dd6aSAndroid Build Coastguard Worker public int getColumnNumber() { return (tokenLocation != null) ? tokenLocation.getColumnNr() : 1; }; 100*78c4dd6aSAndroid Build Coastguard Worker @Override 101*78c4dd6aSAndroid Build Coastguard Worker public String getFilename() { return (tokenLocation != null) ? tokenLocation.getSourceRef().toString() : ""; }; 102*78c4dd6aSAndroid Build Coastguard Worker } 103*78c4dd6aSAndroid Build Coastguard Worker 104*78c4dd6aSAndroid Build Coastguard Worker public static class MyNullNode extends NullNode implements LocationProvider 105*78c4dd6aSAndroid Build Coastguard Worker { 106*78c4dd6aSAndroid Build Coastguard Worker final LocationDetails locDetails; 107*78c4dd6aSAndroid Build Coastguard Worker 108*78c4dd6aSAndroid Build Coastguard Worker public MyNullNode(JsonLocation tokenLocation, JsonLocation currentLocation) 109*78c4dd6aSAndroid Build Coastguard Worker { 110*78c4dd6aSAndroid Build Coastguard Worker super(); 111*78c4dd6aSAndroid Build Coastguard Worker locDetails = new LocationDetailsImpl(tokenLocation, currentLocation); 112*78c4dd6aSAndroid Build Coastguard Worker } 113*78c4dd6aSAndroid Build Coastguard Worker 114*78c4dd6aSAndroid Build Coastguard Worker @Override 115*78c4dd6aSAndroid Build Coastguard Worker public LocationDetails getLocationDetails() 116*78c4dd6aSAndroid Build Coastguard Worker { 117*78c4dd6aSAndroid Build Coastguard Worker return locDetails; 118*78c4dd6aSAndroid Build Coastguard Worker } 119*78c4dd6aSAndroid Build Coastguard Worker } 120*78c4dd6aSAndroid Build Coastguard Worker 121*78c4dd6aSAndroid Build Coastguard Worker public static class MyTextNode extends TextNode implements LocationProvider 122*78c4dd6aSAndroid Build Coastguard Worker { 123*78c4dd6aSAndroid Build Coastguard Worker final LocationDetails locDetails; 124*78c4dd6aSAndroid Build Coastguard Worker 125*78c4dd6aSAndroid Build Coastguard Worker public MyTextNode(String v, JsonLocation tokenLocation, JsonLocation currentLocation) 126*78c4dd6aSAndroid Build Coastguard Worker { 127*78c4dd6aSAndroid Build Coastguard Worker super(v); 128*78c4dd6aSAndroid Build Coastguard Worker locDetails = new LocationDetailsImpl(tokenLocation, currentLocation); 129*78c4dd6aSAndroid Build Coastguard Worker } 130*78c4dd6aSAndroid Build Coastguard Worker 131*78c4dd6aSAndroid Build Coastguard Worker @Override 132*78c4dd6aSAndroid Build Coastguard Worker public LocationDetails getLocationDetails() { return locDetails;} 133*78c4dd6aSAndroid Build Coastguard Worker } 134*78c4dd6aSAndroid Build Coastguard Worker 135*78c4dd6aSAndroid Build Coastguard Worker public static class MyIntNode extends IntNode implements LocationProvider 136*78c4dd6aSAndroid Build Coastguard Worker { 137*78c4dd6aSAndroid Build Coastguard Worker final LocationDetails locDetails; 138*78c4dd6aSAndroid Build Coastguard Worker 139*78c4dd6aSAndroid Build Coastguard Worker public MyIntNode(int v, JsonLocation tokenLocation, JsonLocation currentLocation) 140*78c4dd6aSAndroid Build Coastguard Worker { 141*78c4dd6aSAndroid Build Coastguard Worker super(v); 142*78c4dd6aSAndroid Build Coastguard Worker locDetails = new LocationDetailsImpl(tokenLocation, currentLocation); 143*78c4dd6aSAndroid Build Coastguard Worker } 144*78c4dd6aSAndroid Build Coastguard Worker 145*78c4dd6aSAndroid Build Coastguard Worker @Override 146*78c4dd6aSAndroid Build Coastguard Worker public LocationDetails getLocationDetails() { return locDetails;} 147*78c4dd6aSAndroid Build Coastguard Worker } 148*78c4dd6aSAndroid Build Coastguard Worker 149*78c4dd6aSAndroid Build Coastguard Worker public static class MyBooleanNode extends BooleanNode implements LocationProvider 150*78c4dd6aSAndroid Build Coastguard Worker { 151*78c4dd6aSAndroid Build Coastguard Worker final LocationDetails locDetails; 152*78c4dd6aSAndroid Build Coastguard Worker 153*78c4dd6aSAndroid Build Coastguard Worker public MyBooleanNode(boolean v, JsonLocation tokenLocation, JsonLocation currentLocation) 154*78c4dd6aSAndroid Build Coastguard Worker { 155*78c4dd6aSAndroid Build Coastguard Worker super(v); 156*78c4dd6aSAndroid Build Coastguard Worker locDetails = new LocationDetailsImpl(tokenLocation, currentLocation); 157*78c4dd6aSAndroid Build Coastguard Worker } 158*78c4dd6aSAndroid Build Coastguard Worker 159*78c4dd6aSAndroid Build Coastguard Worker @Override 160*78c4dd6aSAndroid Build Coastguard Worker public LocationDetails getLocationDetails() { return locDetails;} 161*78c4dd6aSAndroid Build Coastguard Worker } 162*78c4dd6aSAndroid Build Coastguard Worker 163*78c4dd6aSAndroid Build Coastguard Worker public static class MyArrayNode extends ArrayNode implements LocationProvider 164*78c4dd6aSAndroid Build Coastguard Worker { 165*78c4dd6aSAndroid Build Coastguard Worker final LocationDetails locDetails; 166*78c4dd6aSAndroid Build Coastguard Worker 167*78c4dd6aSAndroid Build Coastguard Worker public MyArrayNode(JsonNodeFactory nc, JsonLocation tokenLocation, JsonLocation currentLocation) 168*78c4dd6aSAndroid Build Coastguard Worker { 169*78c4dd6aSAndroid Build Coastguard Worker super(nc); 170*78c4dd6aSAndroid Build Coastguard Worker locDetails = new LocationDetailsImpl(tokenLocation, currentLocation); 171*78c4dd6aSAndroid Build Coastguard Worker } 172*78c4dd6aSAndroid Build Coastguard Worker 173*78c4dd6aSAndroid Build Coastguard Worker @Override 174*78c4dd6aSAndroid Build Coastguard Worker public LocationDetails getLocationDetails() { return locDetails;} 175*78c4dd6aSAndroid Build Coastguard Worker } 176*78c4dd6aSAndroid Build Coastguard Worker 177*78c4dd6aSAndroid Build Coastguard Worker public static class MyObjectNode extends ObjectNode implements LocationProvider 178*78c4dd6aSAndroid Build Coastguard Worker { 179*78c4dd6aSAndroid Build Coastguard Worker final LocationDetails locDetails; 180*78c4dd6aSAndroid Build Coastguard Worker 181*78c4dd6aSAndroid Build Coastguard Worker public MyObjectNode(JsonNodeFactory nc, JsonLocation tokenLocation, JsonLocation currentLocation) 182*78c4dd6aSAndroid Build Coastguard Worker { 183*78c4dd6aSAndroid Build Coastguard Worker super(nc); 184*78c4dd6aSAndroid Build Coastguard Worker locDetails = new LocationDetailsImpl(tokenLocation, currentLocation); 185*78c4dd6aSAndroid Build Coastguard Worker } 186*78c4dd6aSAndroid Build Coastguard Worker 187*78c4dd6aSAndroid Build Coastguard Worker @Override 188*78c4dd6aSAndroid Build Coastguard Worker public LocationDetails getLocationDetails() { return locDetails;} 189*78c4dd6aSAndroid Build Coastguard Worker } 190*78c4dd6aSAndroid Build Coastguard Worker``` 191*78c4dd6aSAndroid Build Coastguard Worker 192*78c4dd6aSAndroid Build Coastguard Worker### Scenario 1, solution part 3 - using the custom `JsonNodeFactory` 193*78c4dd6aSAndroid Build Coastguard Worker 194*78c4dd6aSAndroid Build Coastguard WorkerWith the pieces we now have, we just need to tell the YAML library to make of use them, which involves a minor and simple modification to the normal sequence of processing. 195*78c4dd6aSAndroid Build Coastguard Worker 196*78c4dd6aSAndroid Build Coastguard Worker```java 197*78c4dd6aSAndroid Build Coastguard Worker this.yamlFactory = new YAMLFactory(); 198*78c4dd6aSAndroid Build Coastguard Worker 199*78c4dd6aSAndroid Build Coastguard Worker try (YAMLParser yp = yamlFactory.createParser(f);) 200*78c4dd6aSAndroid Build Coastguard Worker { 201*78c4dd6aSAndroid Build Coastguard Worker ObjectReader rdr = mapper.reader(new MyNodeFactory(yp)); 202*78c4dd6aSAndroid Build Coastguard Worker JsonNode jsonNode = rdr.readTree(yp); 203*78c4dd6aSAndroid Build Coastguard Worker Set<ValidationMessage> msgs = mySchema.validate(jsonNode); 204*78c4dd6aSAndroid Build Coastguard Worker 205*78c4dd6aSAndroid Build Coastguard Worker if (msgs.isEmpty()) 206*78c4dd6aSAndroid Build Coastguard Worker { 207*78c4dd6aSAndroid Build Coastguard Worker for (JsonNode item : jsonNode.get("someItem")) 208*78c4dd6aSAndroid Build Coastguard Worker { 209*78c4dd6aSAndroid Build Coastguard Worker processJsonItems(item); 210*78c4dd6aSAndroid Build Coastguard Worker } 211*78c4dd6aSAndroid Build Coastguard Worker } 212*78c4dd6aSAndroid Build Coastguard Worker else 213*78c4dd6aSAndroid Build Coastguard Worker { 214*78c4dd6aSAndroid Build Coastguard Worker // ... we'll look at how to get line locations for ValidationMessage cases in Scenario 2 215*78c4dd6aSAndroid Build Coastguard Worker } 216*78c4dd6aSAndroid Build Coastguard Worker 217*78c4dd6aSAndroid Build Coastguard Worker } 218*78c4dd6aSAndroid Build Coastguard Worker // a JsonProcessingException seems to be the base exception for "gross" errors e.g. 219*78c4dd6aSAndroid Build Coastguard Worker // missing quotes at end of string etc. 220*78c4dd6aSAndroid Build Coastguard Worker catch (JsonProcessingException jpEx) 221*78c4dd6aSAndroid Build Coastguard Worker { 222*78c4dd6aSAndroid Build Coastguard Worker JsonLocation loc = jpEx.getLocation(); 223*78c4dd6aSAndroid Build Coastguard Worker // ... do something with the loc details 224*78c4dd6aSAndroid Build Coastguard Worker } 225*78c4dd6aSAndroid Build Coastguard Worker``` 226*78c4dd6aSAndroid Build Coastguard WorkerSome notes on what is happening here: 227*78c4dd6aSAndroid Build Coastguard Worker 228*78c4dd6aSAndroid Build Coastguard Worker* We instantiate our custom JsonNodeFactory with the YAMLParser reference, and the line locations get recorded for us as the file is parsed. 229*78c4dd6aSAndroid Build Coastguard Worker* If any exceptions are thrown, they will already contain a JsonLocation object that we can use directly if needed 230*78c4dd6aSAndroid Build Coastguard Worker* If we get no validation messages, we know the JSON tree matches the schema and we can do any post processing we need on the tree. We'll see how to report any issues with this in the next part 231*78c4dd6aSAndroid Build Coastguard Worker* We'll look at how to get line locations for ValidationMessage errors in Scenario 2 232*78c4dd6aSAndroid Build Coastguard Worker 233*78c4dd6aSAndroid Build Coastguard Worker### Scenario 1, solution part 4 - extracting the line details 234*78c4dd6aSAndroid Build Coastguard Worker 235*78c4dd6aSAndroid Build Coastguard WorkerHaving got everything prepared, actually getting the line locations is rather easy 236*78c4dd6aSAndroid Build Coastguard Worker 237*78c4dd6aSAndroid Build Coastguard Worker 238*78c4dd6aSAndroid Build Coastguard Worker```java 239*78c4dd6aSAndroid Build Coastguard Worker void processJsonItems(JsonNode item) 240*78c4dd6aSAndroid Build Coastguard Worker { 241*78c4dd6aSAndroid Build Coastguard Worker Iterator<Map.Entry<String, JsonNode>> iter = item.fields(); 242*78c4dd6aSAndroid Build Coastguard Worker 243*78c4dd6aSAndroid Build Coastguard Worker while (iter.hasNext()) 244*78c4dd6aSAndroid Build Coastguard Worker { 245*78c4dd6aSAndroid Build Coastguard Worker Map.Entry<String, JsonNode> node = iter.next(); 246*78c4dd6aSAndroid Build Coastguard Worker extractErrorLocation(node.getValue()); 247*78c4dd6aSAndroid Build Coastguard Worker } 248*78c4dd6aSAndroid Build Coastguard Worker } 249*78c4dd6aSAndroid Build Coastguard Worker 250*78c4dd6aSAndroid Build Coastguard Worker void extractErrorLocation(JsonNode node) 251*78c4dd6aSAndroid Build Coastguard Worker { 252*78c4dd6aSAndroid Build Coastguard Worker if (node == null || !(node instanceof LocationProvider)) { return; } 253*78c4dd6aSAndroid Build Coastguard Worker 254*78c4dd6aSAndroid Build Coastguard Worker //Note: we also know the "span" of the error section i.e. from token location to current location (first char after the token) 255*78c4dd6aSAndroid Build Coastguard Worker // if we wanted at some stage we could use this to highlight/underline all of the text in error 256*78c4dd6aSAndroid Build Coastguard Worker LocationDetails dets = ((LocationProvider) node).getLocationDetails(); 257*78c4dd6aSAndroid Build Coastguard Worker // ... do something with the details e.g. report an error/issue against the YAML line 258*78c4dd6aSAndroid Build Coastguard Worker } 259*78c4dd6aSAndroid Build Coastguard Worker``` 260*78c4dd6aSAndroid Build Coastguard Worker 261*78c4dd6aSAndroid Build Coastguard WorkerSo that's pretty much it - as we are processing the JSON tree, if there is any point we want to report something about the contents, we can do so with a reference back to the original YAML line number. 262*78c4dd6aSAndroid Build Coastguard Worker 263*78c4dd6aSAndroid Build Coastguard WorkerThere is still a problem though, what if the validation against the schema fails? 264*78c4dd6aSAndroid Build Coastguard Worker 265*78c4dd6aSAndroid Build Coastguard Worker## Scenario 2 - ValidationMessage line locations 266*78c4dd6aSAndroid Build Coastguard Worker 267*78c4dd6aSAndroid Build Coastguard WorkerAny failures validation against the schema come back in the form of a set of `ValidationMessage` objects. But these also do not contain original YAML source line information, and there's no easy way to inject it as we did for Scenario 1. Luckily though, there is a trick we can use here! 268*78c4dd6aSAndroid Build Coastguard Worker 269*78c4dd6aSAndroid Build Coastguard WorkerWithin the `ValidationMessage` object is something called the 'path' of the error, which we can access with the `getPath()` method. The syntax of this path by default is close to being [JSONPath](https://datatracker.ietf.org/doc/draft-ietf-jsonpath-base/), but can be set explicitly to be 270*78c4dd6aSAndroid Build Coastguard Workereither [JSONPath](https://datatracker.ietf.org/doc/draft-ietf-jsonpath-base/) or [JSONPointer](https://www.rfc-editor.org/rfc/rfc6901.html) expressions. In our case as we already use [Jackson](https://github.com/FasterXML/jackson) which supports node lookups based on JSONPointer expressions, 271*78c4dd6aSAndroid Build Coastguard Workerwe will set the path expressions to be JSONPointers. This is achieved by configuring the reported path type through the `SchemaValidatorsConfig` before we read our schema: 272*78c4dd6aSAndroid Build Coastguard Worker 273*78c4dd6aSAndroid Build Coastguard Worker```java 274*78c4dd6aSAndroid Build Coastguard Worker SchemaValidatorsConfig config = new SchemaValidatorsConfig(); 275*78c4dd6aSAndroid Build Coastguard Worker config.setPathType(PathType.JSON_POINTER); 276*78c4dd6aSAndroid Build Coastguard Worker JsonSchema jsonSchema = JsonSchemaFactory.getInstance().getSchema(schema, config); 277*78c4dd6aSAndroid Build Coastguard Worker``` 278*78c4dd6aSAndroid Build Coastguard Worker 279*78c4dd6aSAndroid Build Coastguard WorkerHaving set paths to be JSONPointer expressions we can use those pointers for locating the appropriate `JsonNode` instances. The following couple of methods illustrate this process: 280*78c4dd6aSAndroid Build Coastguard Worker 281*78c4dd6aSAndroid Build Coastguard Worker```java 282*78c4dd6aSAndroid Build Coastguard Worker JsonNode findJsonNode(ValidationMessage msg, JsonNode rootNode) 283*78c4dd6aSAndroid Build Coastguard Worker { 284*78c4dd6aSAndroid Build Coastguard Worker // Construct the JSONPointer. 285*78c4dd6aSAndroid Build Coastguard Worker JsonPointer pathPtr = JsonPointer.valueOf(msg.getPath()); 286*78c4dd6aSAndroid Build Coastguard Worker // Now see if we can find the node. 287*78c4dd6aSAndroid Build Coastguard Worker JsonNode node = rootNode.at(pathPtr); 288*78c4dd6aSAndroid Build Coastguard Worker return node; 289*78c4dd6aSAndroid Build Coastguard Worker } 290*78c4dd6aSAndroid Build Coastguard Worker 291*78c4dd6aSAndroid Build Coastguard Worker LocationDetails getLocationDetails(ValidationMessage msg, JsonNode rootNode) 292*78c4dd6aSAndroid Build Coastguard Worker { 293*78c4dd6aSAndroid Build Coastguard Worker LocationDetails retval = null; 294*78c4dd6aSAndroid Build Coastguard Worker JsonNode node = findJsonNode(msg, rootNode); 295*78c4dd6aSAndroid Build Coastguard Worker if (node != null && node instanceof LocationProvider) 296*78c4dd6aSAndroid Build Coastguard Worker { 297*78c4dd6aSAndroid Build Coastguard Worker retval = ((LocationProvider) node).getLocationDetails(); 298*78c4dd6aSAndroid Build Coastguard Worker } 299*78c4dd6aSAndroid Build Coastguard Worker return retval; 300*78c4dd6aSAndroid Build Coastguard Worker } 301*78c4dd6aSAndroid Build Coastguard Worker``` 302*78c4dd6aSAndroid Build Coastguard Worker 303*78c4dd6aSAndroid Build Coastguard Worker## Summary 304*78c4dd6aSAndroid Build Coastguard Worker 305*78c4dd6aSAndroid Build Coastguard WorkerAlthough not trivial, the steps outlined here give us a way to track back to the original source YAML for a variety of possible reporting cases: 306*78c4dd6aSAndroid Build Coastguard Worker 307*78c4dd6aSAndroid Build Coastguard Worker* JSON processing exceptions (mostly already done for us) 308*78c4dd6aSAndroid Build Coastguard Worker* Issues flagged during validation of the YAML against the schema 309*78c4dd6aSAndroid Build Coastguard Worker* Anything we need to report with source information during post processing of the validated JSON tree 310