18

So I'm working on the below json:

{
   "id": "",
   "owner": "some dude",
   "metaData": {
      "request": {
         "ref": null,
         "contacts":[
            {
               "email": null,
               "name": null,
               "contactType": "R"
            },
            {
               "email": null,
               "name": "Dante",
               "contactType": "S"
            }
         ]
      }
   }
}

I want to retrieve the name of contact has type S and only the first one that returned.

Using jsonpath with this path "$..contacts[?(@.contactType == 'S')].name" always return an array of string because a filter operation always return result as an array.

So I tried "$..contacts[?(@.contactType == 'S')].name[0]" and "$..contacts[?(@.contactType == 'S')][0].name" but no luck. Those path returns empty results.

So my question is, is there any way to get only first element when using filter in jsonpath. I'm currently using jayway jsonpath v2.2.0.

Austin
  • 8,018
  • 2
  • 31
  • 37
Vu Viet Dung
  • 343
  • 1
  • 3
  • 6

3 Answers3

3

The quickest solution I found was just comparing it with a list, so like this in your scenario:

.andExpect(jsonPath("$..contacts[?(@.contactType == 'S')].name", equalTo(Arrays.asList("Dante"))))

(Assuming you are trying to compare some result in a test of course)

Vegard
  • 4,352
  • 1
  • 27
  • 25
1

If you use jsonpath with MockMvc class from spring-test, then you may write the following dummy matcher:

import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.core.IsEqual;

import net.minidev.json.JSONArray;

public class FirstMatcher<T> extends BaseMatcher<T> {

    private final Matcher<?> matcher;

    public static <T> FirstMatcher<T> matcher(Matcher<T> matcher) {
        return new FirstMatcher<T>(matcher);
    }

    public static FirstMatcher<Object> value(Object value) {
        return new FirstMatcher<Object>(value);
    }

    public FirstMatcher(Matcher<T> matcher) {
        this.matcher = matcher;
    }

    public FirstMatcher(Object value) {
        this.matcher = new IsEqual<Object>(value); 
    }

    @Override
    public void describeTo(Description description) {
        description.appendText("first matcher");
    }

    @Override
    public boolean matches(Object item) {
        if (!(item instanceof JSONArray)) {
            return false;
        }

        JSONArray array = (JSONArray)item;
        if (array.isEmpty()) {
            return false;
        }

        Object obj = array.get(0);
        return matcher.matches(obj);
    }

}

And use the following way:

mockMvc.
    perform(get(url).
            accept(MediaType.APPLICATION_JSON).accept(MediaType.TEXT_PLAIN).accept(MediaType.ALL)).
    andExpect(status().isOk()).
    andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8)).
    andExpect(jsonPath("$.properties[?(@.propertyName == 'name1')].description").value(FirstMatcher.matcher(IsNull.nullValue()))).
    andExpect(jsonPath("$.properties[?(@.propertyName == 'name2')].required").value(FirstMatcher.value(true)));

P.S. Since net.minidev.json.JSONArray subclasses java.util.List, one may cast to List or even Iterable rather than to net.minidev.json.JSONArray. :)

capocannoniere
  • 143
  • 1
  • 5
0

Unfortunatelly, it is not possible to do this if using the Jayway JsonPath implemenation. You need to resolve the result manually to a list and then do what you need with it.

For the people working with Spring Boot and using WebTestClient for a test, you can do the following:

webTestClient.get().uri("/path").exchange().expectBody().jsonPath("$.dogs[?(@.name == 'Jhon')].pawns").value(value -> {
    var list = (JSONArray) value;
    if (!list.get(0).toString().contains("4")) {
        Assertions.fail("Jhon does not has 4 pawns");
    }
});

The idea is just to call the value method that will allow you to get the result from the jsonpath filter and then you can manually check the value yourself.

Artenes Nogueira
  • 1,422
  • 1
  • 14
  • 23