Repository: networknt/json-schema-validator (java)

Issue:
NPE during JSON schema walk with validation
We ran into this while migrating to 1.5.5. A JSON schema walk returned an NPE rather than returning validation messages while validating a YAML document that did not have the corresponding properties. Below is the smallest recreate I've found that still fails with an NPE. I've replaced the YAML nodes with an instance of the MissingNode below to simulate the missing object properties.

```java
import com.fasterxml.jackson.databind.node.MissingNode;
import com.networknt.schema.JsonSchema;
import com.networknt.schema.JsonSchemaFactory;
import com.networknt.schema.SpecVersion;

public class SchemaTest {

    public static void main(String[] args) {
        String schemaContents = """
            {
                "type": "object",
                "properties": {
                    "field": {
                    "anyOf": [
                        {
                        "type": "string"
                        }
                    ]
                    }
                }
            }
            """;

        JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7);
        JsonSchema schema = factory.getSchema(schemaContents);
        schema.walk(MissingNode.getInstance(), true).getValidationMessages();
    }
}
```

Stack trace:
```
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "com.fasterxml.jackson.databind.JsonNode.getNodeType()" because "node" is null
        at com.networknt.schema.TypeFactory.getValueNodeType(TypeFactory.java:78)
        at com.networknt.schema.utils.JsonNodeUtil.equalsToSchemaType(JsonNodeUtil.java:70)
        at com.networknt.schema.TypeValidator.equalsToSchemaType(TypeValidator.java:50)
        at com.networknt.schema.AnyOfValidator.validate(AnyOfValidator.java:84)
        at com.networknt.schema.AnyOfValidator.walk(AnyOfValidator.java:166)
        at com.networknt.schema.JsonSchema.walk(JsonSchema.java:1278)
        at com.networknt.schema.PropertiesValidator.walkSchema(PropertiesValidator.java:197)
        at com.networknt.schema.PropertiesValidator.validate(PropertiesValidator.java:109)
        at com.networknt.schema.PropertiesValidator.walk(PropertiesValidator.java:133)
        at com.networknt.schema.JsonSchema.walk(JsonSchema.java:1278)
        at com.networknt.schema.JsonSchema.walkAtNodeInternal(JsonSchema.java:1247)
        at com.networknt.schema.JsonSchema.walk(JsonSchema.java:1082)
        at com.networknt.schema.JsonSchema.walk(JsonSchema.java:1162)
       ...
```

Pull request description:
Fix NPE when walking a missing node that will have missing properties
Closes #1151 

Fixes the NPE by having `TypeFactory.getValueNodeType((JsonNode node, SchemaValidatorsConfig config)` handle the case where a `null` was passed in as the node.

I had considered if it should be ensured that the node inputs should always be non null and always replace `null` by `MissingNode` instead. For instance having `PropertiesValidator` set the input as `MissingNode` instead of `null`. But since `JsonNode.get(String fieldName)` returns `null` for missing nodes I wasn't sure it would be appropriate.

Patch:
diff --git a/src/main/java/com/networknt/schema/TypeFactory.java b/src/main/java/com/networknt/schema/TypeFactory.java
index b97d36a..cb98605 100644
--- a/src/main/java/com/networknt/schema/TypeFactory.java
+++ b/src/main/java/com/networknt/schema/TypeFactory.java
@@ -75,6 +75,11 @@ public class TypeFactory {
      * @return the json type
      */
     public static JsonType getValueNodeType(JsonNode node, SchemaValidatorsConfig config) {
+        if (node == null) {
+            // This returns JsonType.UNKNOWN to be consistent with the behavior when
+            // JsonNodeType.MISSING
+            return JsonType.UNKNOWN;
+        }
         JsonNodeType type = node.getNodeType();
         switch (type) {
         case OBJECT:


Test patch:
diff --git a/src/test/java/com/networknt/schema/JsonWalkTest.java b/src/test/java/com/networknt/schema/JsonWalkTest.java
index 99d73c6..af1c2ac 100644
--- a/src/test/java/com/networknt/schema/JsonWalkTest.java
+++ b/src/test/java/com/networknt/schema/JsonWalkTest.java
@@ -2,6 +2,7 @@ package com.networknt.schema;
 
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.MissingNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import com.networknt.schema.walk.JsonSchemaWalkListener;
 import com.networknt.schema.walk.WalkEvent;
@@ -16,6 +17,7 @@ import java.util.LinkedHashSet;
 import java.util.Set;
 import java.util.TreeSet;
 
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
 class JsonWalkTest {
@@ -110,6 +112,27 @@ class JsonWalkTest {
                 + "}")));
     }
 
+    @Test
+    void testWalkMissingNodeWithPropertiesSchemaShouldNotThrow() {
+        String schemaContents = "{\n"
+                + "                \"type\": \"object\",\n"
+                + "                \"properties\": {\n"
+                + "                    \"field\": {\n"
+                + "                    \"anyOf\": [\n"
+                + "                        {\n"
+                + "                        \"type\": \"string\"\n"
+                + "                        }\n"
+                + "                    ]\n"
+                + "                    }\n"
+                + "                }\n"
+                + "            }";
+
+        JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7);
+        JsonSchema schema = factory.getSchema(schemaContents);
+        JsonNode missingNode = MissingNode.getInstance();
+        assertDoesNotThrow(() -> schema.walk(missingNode, true));
+    }
+
     private InputStream getSchema() {
         return getClass().getClassLoader().getResourceAsStream("schema/walk-schema.json");
     }