title: Hamcrest with MockMvc: check that key exists but value may be null
body: <p>I'm doing some tests with MockMvc, and I want to validate the structure of a JSON response. Specifically, I want to make sure that the key to an attribute exists, and that the value is of a certain type or null.</p>
  
  <pre><code>{
     "keyToNull": null,  # This may be null, or a String
     "keyToString": "some value"
  }
  </code></pre>
  
  <p>The following works for me, but I'm wondering if there's a way to combine each group of two expectations into a single line, as I have a lot of attributes to check:</p>
  
  <pre><code>import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
  import static org.hamcrest.Matchers.*;
  
  .andExpect(jsonPath("$").value(hasKey("keyToNull")))
  .andExpect(jsonPath("$.keyToNull").value(
                    anyOf(any(String.class), nullValue(String.class))))
  
  .andExpect(jsonPath("$").value(hasKey("keyToString")))
  .andExpect(jsonPath("$.keyToString").value(
                    anyOf(any(String.class), nullValue(String.class))))
  </code></pre>
  
  <p>The <code>hasKey()</code> is necessary because the other assertion by itself passes since nonexistent keys in MockMvc's implementation map to null:</p>
  
  <pre><code>.andExpect(jsonPath("$.notAKey").value(
                    anyOf(any(String.class), nullValue(String.class)))) // ok
  </code></pre>
  
  <p><code>jsonPath().exists()</code> also doesn't work, because internally it compares the value against <code>null</code>.</p>
  
  <p>I've considered making a separate method like this:</p>
  
  <pre><code>private static &lt;T&gt; void assertNullableAttr(ResultActions res, String jsonPath, Class&lt;T&gt; type) throws Exception {
      int split = jsonPath.lastIndexOf('.');
      String prefix = jsonPath.substring(0, split), key = jsonPath.substring(split+1);
  
      res.andExpect(jsonPath(prefix).value(hasKey(key)))
          .andExpect(jsonPath(jsonPath).value(anyOf(any(type), nullValue(type))));
      }
  </code></pre>
  
  <p>But then it forces me to split my code in an unnatural way:</p>
  
  <pre><code>ResultActions res = mockMvc.perform(get("/api"))
      // these attributes must not be null
      .andExpect(jsonPath("$.someInfo").value(hasSize(2)))
          .andExpect(jsonPath("$.someInfo[0].info1").value(any(String.class)))
          .andExpect(jsonPath("$.someInfo[0].info2").value(any(String.class)))
      .andExpect(jsonPath("$.addlInfo").value(hasSize(2)))
          .andExpect(jsonPath("$.addlInfo[0].info1").value(any(String.class)))
          .andExpect(jsonPath("$.addlInfo[0].info2").value(any(String.class)));
  
  // these attributes may be null
  assertNullableAttr(res, "$.someInfo[0].info3", String.class);
  assertNullableAttr(res, "$.someInfo[0].info4", String.class);
  
  assertNullableAttr(res, "$.addlInfo[0].info3", String.class);
  assertNullableAttr(res, "$.addlInfo[0].info4", String.class);
  </code></pre>
  
  <p>Is there any clever Hamcrest Matcher I could apply to a single json path per attribute?</p>