How to count members with jsonpath?
Asked Answered
F

7

139

Is it possible to count the number of members using JsonPath?

Using Spring MVC test I'm testing a controller that generates

{"foo": "oof", "bar": "rab"}

with:

standaloneSetup(new FooController(fooService)).build()
    .perform(get("/something").accept(MediaType.APPLICATION_JSON)).andExpect(status().isOk())
    .andExpect(jsonPath("$.foo").value("oof"))
    .andExpect(jsonPath("$.bar").value("rab"));

I'd like to make sure that no other members are present in the generated json. Hopefully by counting them using jsonPath. Is it possible? Alternate solutions are welcome too.

Filmore answered 6/12, 2012 at 14:8 Comment(0)
P
325

To test size of array: jsonPath("$", hasSize(4))

To count members of object: jsonPath("$.*", hasSize(4))


I.e. to test that API returns an array of 4 items:

accepted value: [1,2,3,4]

mockMvc.perform(get(API_URL))
       .andExpect(jsonPath("$", hasSize(4)));

to test that API returns an object containing 2 members:

accepted value: {"foo": "oof", "bar": "rab"}

mockMvc.perform(get(API_URL))
       .andExpect(jsonPath("$.*", hasSize(2)));

I'm using Hamcrest version 1.3 and Spring Test 3.2.5.RELEASE

hasSize(int) javadoc

Note: You need to include hamcrest-library dependency and import static org.hamcrest.Matchers.*; for hasSize() to work.

Periodontal answered 10/12, 2013 at 15:52 Comment(5)
@mattb - if using Maven, do not add hamcrest-all as a dependancy, but use hamcrest-library: code.google.com/p/hamcrest/wiki/HamcrestDistributablesInternode
What if one does not know the size and wants to get it?Apanage
@menuka-ishan - I don't think it's deprecated, according to: MockMvcResultMatchers.jsonPath() javadocPeriodontal
@Apanage you must count up the size of all the fields on your Object/Array, either from source code or web browser developer tools inspection of the response.Keeler
For kotlin you may need to add type argument like so: .andExpect(jsonPath("$", hasSize<Array<Any>>(4)))Repulsive
D
29

You can also use the methods inside the jsonpath, so instead of

mockMvc.perform(get(API_URL))
   .andExpect(jsonPath("$.*", hasSize(2)));

you can do

mockMvc.perform(get(API_URL))
   .andExpect(jsonPath("$.length()", is(2)));
Dashtilut answered 22/11, 2018 at 14:42 Comment(0)
S
13

We can use JsonPath functions like size() or length(), like this:

@Test
public void givenJson_whenGetLengthWithJsonPath_thenGetLength() {
    String jsonString = "{'username':'jhon.user','email':'[email protected]','age':'28'}";

    int length = JsonPath
        .parse(jsonString)
        .read("$.length()");

    assertThat(length).isEqualTo(3);
}

Or simply parsing to net.minidev.json.JSONObject and get the size:

@Test
public void givenJson_whenParseObject_thenGetSize() {
    String jsonString = "{'username':'jhon.user','email':'[email protected]','age':'28'}";

    JSONObject jsonObject = (JSONObject) JSONValue.parse(jsonString);

    assertThat(jsonObject)
        .size()
        .isEqualTo(3);
}

Indeed, the second approach looks to perform better than the first one. I made a JMH (Java Microbenchmark Harness) performance test and I get the following results:

| Benchmark                                       | Mode  | Cnt | Score       | Error        | Units |
|-------------------------------------------------|-------|-----|-------------|--------------|-------|
| JsonPathBenchmark.benchmarkJSONObjectParse      | thrpt | 5   | 3241471.044 | ±1718855.506 | ops/s |
| JsonPathBenchmark.benchmarkJsonPathObjectLength | thrpt | 5   | 1680492.243 | ±132492.697  | ops/s |

The example code can be found here.

Saloma answered 6/8, 2018 at 17:8 Comment(2)
JMH? What does this stand for?Kimball
JMH means Java Microbenchmark Harness, see github.com/openjdk/jmhSaloma
S
4

Been dealing with this myself today. It doesn't seem like this is implemented in the available assertions. However, there is a method to pass in an org.hamcrest.Matcher object. With that you can do something like the following:

final int count = 4; // expected count

jsonPath("$").value(new BaseMatcher() {
    @Override
    public boolean matches(Object obj) {
        return obj instanceof JSONObject && ((JSONObject) obj).size() == count;
    }

    @Override
    public void describeTo(Description description) {
        // nothing for now
    }
})
Shote answered 28/12, 2012 at 23:33 Comment(0)
A
2

Tried with WebTestClient similar approach:

webTestClient.get()
    .uri(KEYS_MANAGEMENT_URI)
    .header(
        HttpHeaders.AUTHORIZATION,
        createBearerToken(createJwtClaims(KEYS_AUTHORITY_VIEW))
    )
    .exchange()
    .expectStatus().isOk()
    .expectBody()
    .jsonPath("data").isArray()
    .jsonPath("data.length()").isEqualTo(1)
    .jsonPath("data[0].id").isEqualTo(KEY_ID)

And assertions worked fine.

Alehouse answered 25/10, 2022 at 14:46 Comment(0)
G
0

if you don't have com.jayway.jsonassert.JsonAssert on your classpath (which was the case with me), testing in the following way may be a possible workaround:

assertEquals(expectedLength, ((net.minidev.json.JSONArray)parsedContent.read("$")).size());

[note: i assumed that the content of the json is always an array]

Gaea answered 27/11, 2015 at 20:55 Comment(0)
A
0

For those, who use Kotlin: org.hamcrest.Matchers#is(T) will help you.
Here is an example, if your root json element is array:

[
  {
    "someProperty": "someValue"
  },
  {
    "someProperty": "someValue"
  }
]
mvc.get("/your-endpoint")
            .andExpect {
                status { isOk() }
                content { contentType(MediaType.APPLICATION_JSON) }
                content {
                    jsonPath("$.length()", `is`(2))
                }
            }
Ashien answered 18/11, 2023 at 12:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.