JsonUnit

repository·master·Indexed 21 days ago

https://github.com/lukas-krecan/jsonunit

A library for simplifying JSON comparison in tests, providing deep and flexible assertions through integrations with AssertJ, Hamcrest, Spring MVC (MockMvc, WebTestClient, RestTestClient), and Kotest. It supports JsonPath navigation, type placeholders, custom matchers, and options such as ignoring array order or specific JSON paths.

Tokens
10K
Snippets
28
Records
30
Agent score
27%

What's inside JsonUnit

  1. Use JsonPath for navigation

    master

    JsonPath is supported across all major APIs (AssertJ, Spring, Kotest, and core JsonAssert). It allows you to navigate to specific parts of a JSON document for targeted assertions.

    Examples:

    • AssertJ: .inPath("$.store.book")
    • JsonAssert: assertJsonPartEquals("[1]", ...)
    • Spring: .andExpect(json().inPath("$.result.array[1]").isEqualTo(2))
    • Kotest: inPath "$.test[*].a"
    // AssertJ style
    assertThatJson(json)
        .inPath("$.store.book")
        .isArray()
        .contains(json("{\"category\": \"reference\", ...}"));
    
    // JsonAssert API
    assertJsonPartEquals("[1]", "{\"test\":{\"value\":1}}", "$..value");
    
    // Spring
    mockMvc.perform(get("/sample"))
        .andExpect(json().inPath("$.result.array[1]").isEqualTo(2));
  2. Configure JSON comparison options

    master

    JsonUnit provides several options to customize how JSON documents are compared. You can apply these globally using .when(OPTION) or locally to specific paths using .when(path(...), then(OPTION)).

    Global Options:

    • TREATING_NULL_AS_ABSENT: Fields with null values are treated as if the field were missing.
    • IGNORING_ARRAY_ORDER: Ignores the order of elements in arrays.
    • IGNORING_EXTRA_ARRAY_ITEMS: Ignores unexpected items in an array.
    • IGNORING_EXTRA_FIELDS: Ignores extra fields present in the compared value.
    • IGNORE_VALUES: Compares only the types of the values at the specified paths, ignoring the actual values.
    • FAIL_FAST: Stops comparison at the first difference found.
    • REPORTING_DIFFERENCE_AS_NORMALIZED_STRING: Reports errors as normalized strings to improve IDE diff visibility.

    Note: TREATING_NULL_AS_ABSENT and IGNORING_VALUES require exact paths to the fields being targeted.

    // Global application
    assertThatJson("{\"test\":[1,2,3]}")
        .when(IGNORING_ARRAY_ORDER)
        .isEqualTo("{\"test\":[3,2,1]}");
    
    // Local application to specific paths
    assertThatJson("{\"a\":[1,2,3]}")
        .when(path("a"), then(IGNORING_EXTRA_ARRAY_ITEMS))
        .isEqualTo("{\"a\":[1,2]}");
  3. Handle lenient parsing of expected values in AssertJ

    master

    JsonUnit attempts to parse the expected value as JSON. If parsing fails, it treats it as a plain string. This can cause issues with primitive values like numbers or booleans that look like valid JSON but are intended to be strings.

    • To prevent parsing and treat the value as a literal string, wrap it in JsonAssertions.value().
    • To ensure the value is parsed as JSON, wrap it in JsonAssertions.json().
    // This test does NOT pass. "1" is parsed as JSON containing number 1, the actual value is a string.
    assertThatJson("{\"id\":\"1\", \"children\":[{\"parentId\":\"1\"}]}")
        .inPath("children[*].parentId")
        .isArray()
        .containsOnly("1");
    
    // Use value() to prevent parsing
    assertThatJson("{\"id\":\"1\", \"children\":[{\"parentId\":\"1\"}]}")
        .inPath("children[*].parentId")
        .isArray()
        .containsOnly(value("1"));
    
    // Use value() for booleans to ensure they aren't parsed as JSON primitives
    assertThatJson("{\"root\":[\"true\"]}").node("root").isArray().containsExactly(value("true"));
  4. Use Standard assert (JUnit-style)

    master

    The Standard API is a traditional JUnit-like API for users who prefer non-fluent assertions.

    Key features include:

    • assertJsonEquals: Compares two JSON documents or a JSON document against a serialized object. Supports options like TREATING_NULL_AS_ABSENT or IGNORING_VALUES via the when() method.
    • assertJsonPartEquals: Compares only a specific part of a JSON document using a path.
    • Lenient parsing: The expected value can be written with less strict JSON syntax (e.g., missing quotes).
    • Numerical comparison: Supports tolerance-based comparison using withTolerance(double).
    import static net.javacrumbs.jsonunit.JsonAssert.*;
    import static net.javacrumbs.jsonunit.core.Option.*;
    
    // compares two JSON documents
    assertJsonEquals("{"test":1}", "{\n"test": 1\n}");
    
    // objects are automatically serialized before comparison
    assertJsonEquals(jsonObject, "{\n"test": 1\n}");
    
    // compares only part
    assertJsonPartEquals("2", "{"test":[{"value":1},{"value":2}]}",
        "test[1].value");
    
    // extra options can be specified
    assertJsonEquals("{"test":{"a":1}}",
        "{"test":{"a":1, "b": null}}",
        when(TREATING_NULL_AS_ABSENT));
    
    // compares only the structure, not the values
    assertJsonEquals("[{"test":1}, {"test":2}]",
        "[{\n"test": 1\n}, {"TEST": 4}]", when(IGNORING_VALUES));
    
    // Lenient parsing of expected value
    assertJsonEquals("{//Look ma, no quotation marks\n test:'value'}",
        "{\n"test": "value"\n}");
    
    // Numerical comparison
    assertJsonEquals("1", "\n1.009\n", withTolerance(0.01));
  5. Use Spring RestTestClient with JsonUnit

    master

    JsonUnit provides seamless integration with Spring's RestTestClient. Use RestTestClientJsonMatcher.json() to perform assertions on the response body.

    import static net.javacrumbs.jsonunit.spring.RestTestClientJsonMatcher.json;
    
    // ...
    
    client.get().uri(path).accept(MediaType.APPLICATION_JSON)
        .exchange()
        .expectBody()
        .consumeWith(json().isEqualTo(CORRECT_JSON));
    
    client.get().uri(path).exchange().expectBody()
        .consumeWith(json().node("result.string").isEqualTo("stringValue"));
    
    client.get().uri(path).exchange().expectBody()
        .consumeWith(json().inPath("$.result.array[1]").isEqualTo(2));
    
    client.get().uri(path).exchange().expectBody()
        .consumeWith(json().node("result.array")
            .when(Option.IGNORING_ARRAY_ORDER)
            .isEqualTo(new int[]{3, 2, 1}));
  6. Add Standard assert dependency

    master

    To use the Standard API, add the following dependency to your pom.xml:

    <dependency>
        <groupId>net.javacrumbs.json-unit</groupId>
        <artifactId>json-unit</artifactId>
        <version>2.14.0</version>
        <scope>test</scope>
    </dependency>
  7. Use Spring AssertJ for MockMvc

    master

    Since version 4.0.0, JsonUnit supports AssertJ assertions for Spring MockMvc. You can switch from standard MockMvc assertions to JsonUnit assertions by calling .bodyJson().convertTo(jsonUnitJson()) on the result.

    import static net.javacrumbs.jsonunit.assertj.JsonAssertions.jsonUnitJson;
    
    ...나
    
    assertThat(mvc.get().uri("/sample"))
        .hasStatusOk()
        .bodyJson()
        .convertTo(jsonUnitJson()) // Switch to JsonUnit assert
        .inPath("result.array") // This is JsonUnit
        .isArray()
        .containsExactly(1, 2, 3);
  8. Customize Jackson Object Mapper via SPI

    master

    To customize the Jackson 2 or Jackson 3 ObjectMapper used by JsonUnit, implement the corresponding provider interface and register it using the Java Service Provider Interface (SPI).

    For Jackson 2:

    1. Implement net.javacrumbs.jsonunit.providers.Jackson2ObjectMapperProvider.
    2. Register it in META-INF/services/net.javacrumbs.jsonunit.providers.Jackson2ObjectMapperProvider.

    For Jackson 3:

    1. Implement net.javacrumbs.jsonunit.providers.Jackson3ObjectMapperProvider.
    2. Register it in META-INF/services/net.javacrumbs.jsonunit.providers.Jackson3ObjectMapperProvider.

    Note: Jackson 3 uses the tools.jackson package namespace instead of com.fasterxml.jackson.

    public class Java8ObjectMapperProvider implements Jackson2ObjectMapperProvider {
        // ... implementation ...
        @Override
        public ObjectMapper getObjectMapper(boolean lenient) {
            return lenient ? lenientMapper : mapper;
        }
    }
  9. Install AssertJ integration for JsonUnit

    master

    To use the recommended AssertJ-based API, add the json-unit-assertj dependency to your project. This integration combines JsonUnit's JSON comparison features with AssertJ's fluent assertion style.

    <dependency>
        <groupId>net.javacrumbs.json-unit</groupId>
        <artifactId>json-unit-assertj</artifactId>
        <version>6.0.1</version>
        <scope>test</scope>
    </dependency>
  10. Install Hamcrest integration for JsonUnit

    master

    To use Hamcrest matchers, add the core json-unit dependency to your project.

    <dependency>
        <groupId>net.javacrumbs.json-unit</groupId>
        <artifactId>json-unit</artifactId>
        <version>6.0.1</version>
        <scope>test</scope>
    </dependency>
  11. Add Fluent assertions dependency

    master

    To use the deprecated Fluent API, add the following dependency to your pom.xml:

    <dependency>
        <groupId>net.javacrumbs.json-unit</groupId>
        <artifactId>json-unit-fluent</artifactId>
        <version>2.14.0</version>
        <scope>test</scope>
    </dependency>
  12. Use Fluent assertions (deprecated)

    master

    Fluent assertions provide a FEST/AssertJ-inspired API for JSON testing. While still supported, they are deprecated in favor of the AssertJ integration.

    Key features include:

    • Comparing entire documents or specific nodes using .node(path).
    • Path navigation supports dot notation (e.g., root.test) and array indexing (e.g., test[0]).
    • Negative indexing for arrays (e.g., test[-1] to get the last element).
    • Configuration options (like IGNORING_VALUES or IGNORING_EXTRA_FIELDS) must be specified using .when(...) before the assertion method.
    • Support for array length checks (isArray().ofLength(n)) and content checks (isArray().thatContains(...)).
    • Integration with Hamcrest matchers via the .matches() method.
    import static net.javacrumbs.jsonunit.fluent.JsonFluentAssert.assertThatJson;
    import static net.javacrumbs.jsonunit.core.Option.*;
    
    // compares entire documents
    assertThatJson("{\"test\":1}").isEqualTo("{\"test\":2}");
    
    // compares only parts of the document
    assertThatJson("{\"test1\":2, \"test2\":1}")
        .node("test1").isEqualTo(2)
        .node("test2").isEqualTo(2);
    
    // compare node indexed from start of array
    assertThatJson("{\"root\":{\"test\":[1,2,3]}}")
        .node("root.test[0]").isEqualTo(1);
    
    // compare node indexed from end of array
    assertThatJson("{\"root\":{\"test\":[1,2,3]}}")
        .node("root.test[-1]").isEqualTo(3);
    
    // compares only the structure (Options must be specified before the assertion)
    assertThatJson("{\"test\":1}")
        .when(IGNORING_VALUES)
        .isEqualTo("{\"test\":21}");
    
    // ignores extra fields
    assertThatJson("{\"test\":{\"a\":1, \"b\":2, \"c\":3}}")
        .when(IGNORING_EXTRA_FIELDS)
        .isEqualTo("{\"test\":{\"b\":2}}");
    
    // array length comparison
    assertThatJson("{\"test\":[1,2,3]}").node("test")
        .isArray().ofLength(2);
    
    // array contains node
    assertThatJson("{\"test\":[{\"id\":36},{\"id\":37}]}").node("test")
        .isArray().thatContains("{\"id\":37}");