From e390389d91f59a6252a4059fdbccdc99e9a55737 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 6 Sep 2026 17:02:36 +0000 Subject: [PATCH 1/6] chore: prepare next development iteration 0.2.0-SNAPSHOT --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 4e1039d..a3653f3 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.archyoshi assertj-json - 0.1.0 + 0.2.0-SNAPSHOT jar AssertJ JSON From 59a82e394fc411166a35744aa5bd36eee3da3b4b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 6 Sep 2026 17:26:33 +0000 Subject: [PATCH 2/6] chore: prepare next development iteration 0.2.0-SNAPSHOT --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a8e70a9..ff57291 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.archyoshi assertj-json - 0.1.1 + 0.2.0-SNAPSHOT jar AssertJ JSON From f96b964363af9af6bd47e624f1e4902be8bd5a68 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 6 Sep 2026 21:46:16 +0000 Subject: [PATCH 3/6] chore: prepare next development iteration 0.2.0-SNAPSHOT --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 7f9e00a..4c34631 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.archyoshi assertj-json - 0.1.2 + 0.2.0-SNAPSHOT jar AssertJ JSON From d9a08f461e20fa4c42cd5857d09e47bab1e201c5 Mon Sep 17 00:00:00 2001 From: Thami Inaflas Date: Mon, 7 Sep 2026 00:01:39 +0200 Subject: [PATCH 4/6] feat: add String, Path, and File overloads to JsonNode assertions and assertThatArray --- pom.xml | 2 +- .../assertj/json/JsonAssertions.java | 115 ++++++++++++- .../json/api/JsonComparisonAssert.java | 113 ++++++++++--- .../assertj/json/api/JsonIterableAssert.java | 74 +++++++++ .../assertj/json/api/JsonNodeAssert.java | 153 +++++++++++++++++- .../assertj/json/api/JsonNodeLoader.java | 101 ++++++++++++ .../assertj/json/JsonAssertionsTest.java | 31 ++++ .../json/api/JsonComparisonAssertTest.java | 20 +++ .../api/JsonNodeAssertAdditionalTest.java | 50 ++++++ 9 files changed, 626 insertions(+), 33 deletions(-) create mode 100644 src/main/java/com/archyoshi/assertj/json/api/JsonNodeLoader.java diff --git a/pom.xml b/pom.xml index 4c34631..2fc91a8 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.archyoshi assertj-json - 0.2.0-SNAPSHOT + 0.1.3-SNAPSHOT jar AssertJ JSON diff --git a/src/main/java/com/archyoshi/assertj/json/JsonAssertions.java b/src/main/java/com/archyoshi/assertj/json/JsonAssertions.java index bae8de6..1eefb4e 100644 --- a/src/main/java/com/archyoshi/assertj/json/JsonAssertions.java +++ b/src/main/java/com/archyoshi/assertj/json/JsonAssertions.java @@ -18,9 +18,11 @@ import com.archyoshi.assertj.json.api.JsonComparisonAssert; import com.archyoshi.assertj.json.api.JsonIterableAssert; import com.archyoshi.assertj.json.api.JsonNodeAssert; +import com.archyoshi.assertj.json.api.JsonNodeLoader; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.File; import java.nio.file.Path; /** @@ -92,7 +94,7 @@ public static JsonNodeAssert assertThat(final String json, final ObjectMapper ma *

The file is read and parsed as JSON content. The default {@code ObjectMapper} is used. * * @param jsonFile the path to the JSON file to assert on - * @return a new {@link JsonNodeAssert} instance + * @return a new {@link JsonComparisonAssert} instance * @throws AssertionError if the file cannot be read or contains invalid JSON * @since 0.1.0 */ @@ -111,6 +113,35 @@ public static JsonComparisonAssert assertThat(final Path jsonFile, final ObjectM return new JsonComparisonAssert(jsonFile, mapper); } + /** + * Creates a new assertion object for the given JSON file. + * + *

The file is read and parsed as JSON content. The default {@code ObjectMapper} is used. + * + * @param jsonFile the JSON file to assert on + * @return a new {@link JsonComparisonAssert} instance + * @throws AssertionError if the file cannot be read or contains invalid JSON + * @since 0.1.2 + */ + public static JsonComparisonAssert assertThat(final File jsonFile) { + return assertThat(jsonFile, MAPPER); + } + + /** + * Creates an assertion object for a JSON file parsed with the supplied mapper. + * + * @param jsonFile the JSON file to assert on + * @param mapper the mapper used to parse JSON + * @return a new {@link JsonComparisonAssert} instance + * @since 0.1.2 + */ + public static JsonComparisonAssert assertThat(final File jsonFile, final ObjectMapper mapper) { + if (jsonFile == null) { + throw new AssertionError("Expected JSON file not to be null"); + } + return assertThat(jsonFile.toPath(), mapper); + } + /** * Creates iterable assertions for a JSON array node. * @@ -123,11 +154,81 @@ public static JsonIterableAssert assertThatArray(final JsonNode actual) { return JsonIterableAssert.assertThat(actual); } - private static JsonNode parseJson(final String json, final ObjectMapper mapper) { - try { - return mapper.readTree(json); - } catch (JsonProcessingException e) { - throw new AssertionError("Invalid JSON content: " + e.getOriginalMessage(), e); - } + /** + * Creates iterable assertions for a JSON array string. + * + * @param jsonArray the JSON array string + * @return an assertion object for the array elements + * @throws AssertionError if the string cannot be parsed or is not a JSON array + * @since 0.1.2 + */ + public static JsonIterableAssert assertThatArray(final String jsonArray) { + return assertThatArray(jsonArray, MAPPER); + } + + /** + * Creates iterable assertions for a JSON array string parsed with the supplied mapper. + * + * @param jsonArray the JSON array string + * @param mapper the mapper used to parse the string + * @return an assertion object for the array elements + * @throws AssertionError if the string cannot be parsed or is not a JSON array + * @since 0.1.2 + */ + public static JsonIterableAssert assertThatArray( + final String jsonArray, final ObjectMapper mapper) { + return assertThatArray(JsonNodeLoader.toNode(jsonArray, mapper)); + } + + /** + * Creates iterable assertions for a JSON array file at the given path. + * + * @param jsonArrayPath the path to the JSON array file + * @return an assertion object for the array elements + * @throws AssertionError if the file cannot be read or is not a JSON array + * @since 0.1.2 + */ + public static JsonIterableAssert assertThatArray(final Path jsonArrayPath) { + return assertThatArray(jsonArrayPath, MAPPER); + } + + /** + * Creates iterable assertions for a JSON array file at the given path using the supplied mapper. + * + * @param jsonArrayPath the path to the JSON array file + * @param mapper the mapper used to parse JSON + * @return an assertion object for the array elements + * @throws AssertionError if the file cannot be read or is not a JSON array + * @since 0.1.2 + */ + public static JsonIterableAssert assertThatArray( + final Path jsonArrayPath, final ObjectMapper mapper) { + return assertThatArray(JsonNodeLoader.toNode(jsonArrayPath, mapper)); + } + + /** + * Creates iterable assertions for a JSON array file. + * + * @param jsonArrayFile the JSON array file + * @return an assertion object for the array elements + * @throws AssertionError if the file cannot be read or is not a JSON array + * @since 0.1.2 + */ + public static JsonIterableAssert assertThatArray(final File jsonArrayFile) { + return assertThatArray(jsonArrayFile, MAPPER); + } + + /** + * Creates iterable assertions for a JSON array file using the supplied mapper. + * + * @param jsonArrayFile the JSON array file + * @param mapper the mapper used to parse JSON + * @return an assertion object for the array elements + * @throws AssertionError if the file cannot be read or is not a JSON array + * @since 0.1.2 + */ + public static JsonIterableAssert assertThatArray( + final File jsonArrayFile, final ObjectMapper mapper) { + return assertThatArray(JsonNodeLoader.toNode(jsonArrayFile, mapper)); } } diff --git a/src/main/java/com/archyoshi/assertj/json/api/JsonComparisonAssert.java b/src/main/java/com/archyoshi/assertj/json/api/JsonComparisonAssert.java index 4d83b97..34ff53f 100644 --- a/src/main/java/com/archyoshi/assertj/json/api/JsonComparisonAssert.java +++ b/src/main/java/com/archyoshi/assertj/json/api/JsonComparisonAssert.java @@ -20,6 +20,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -61,6 +62,23 @@ public JsonComparisonAssert(final Path actual, final ObjectMapper mapper) { this.mapper = mapper; } + /** + * Verifies that the actual JSON file has the same field names as the expected JSON node. + * Values are ignored, while nested object structure and array positions are preserved. + * + * @param expected the expected JSON node + * @return this assertion object + * @since 0.1.2 + */ + public JsonComparisonAssert hasSameFieldsAs(final JsonNode expected) { + final JsonNode actualNode = actualJson(); + if (!sameFields(actualNode, expected)) { + failWithActualExpectedAndMessage( + actualNode, expected, "Expected JSON files to have the same fields"); + } + return this; + } + /** * Verifies that the actual JSON file and expected JSON file contain the same field names. * Values are ignored, while nested object structure and array positions are preserved. @@ -69,7 +87,18 @@ public JsonComparisonAssert(final Path actual, final ObjectMapper mapper) { * @return this assertion object */ public JsonComparisonAssert hasSameFieldsAs(final Path expectedFile) { - return hasSameFieldsAs(readJson(expectedFile)); + return hasSameFieldsAs(JsonNodeLoader.toNode(expectedFile, mapper)); + } + + /** + * Verifies that the actual JSON file and expected JSON file contain the same field names. + * + * @param expectedFile the expected JSON file + * @return this assertion object + * @since 0.1.2 + */ + public JsonComparisonAssert hasSameFieldsAs(final File expectedFile) { + return hasSameFieldsAs(JsonNodeLoader.toNode(expectedFile, mapper)); } /** @@ -79,15 +108,20 @@ public JsonComparisonAssert hasSameFieldsAs(final Path expectedFile) { * @return this assertion object */ public JsonComparisonAssert hasSameFieldsAs(final String expectedJson) { - return hasSameFieldsAs(parseJson(expectedJson)); + return hasSameFieldsAs(JsonNodeLoader.toNode(expectedJson, mapper)); } - private JsonComparisonAssert hasSameFieldsAs(final JsonNode expected) { - JsonNode actualNode = actualJson(); - if (!sameFields(actualNode, expected)) { - failWithActualExpectedAndMessage( - actualNode, expected, "Expected JSON files to have the same fields"); - } + /** + * Verifies that the actual JSON file has the same parsed content as the expected JSON node. + * Formatting and object field ordering are ignored. + * + * @param expected the expected JSON node + * @return this assertion object + * @since 0.1.2 + */ + public JsonComparisonAssert hasSameContentAs(final JsonNode expected) { + final JsonNode actualNode = actualJson(); + assertThat(actualNode).as("JSON content from %s", actual).isEqualTo(expected); return this; } @@ -99,7 +133,18 @@ private JsonComparisonAssert hasSameFieldsAs(final JsonNode expected) { * @return this assertion object */ public JsonComparisonAssert hasSameContentAs(final Path expectedFile) { - return hasSameContentAs(readJson(expectedFile)); + return hasSameContentAs(JsonNodeLoader.toNode(expectedFile, mapper)); + } + + /** + * Verifies that the actual JSON file and expected JSON file have the same parsed content. + * + * @param expectedFile the expected JSON file + * @return this assertion object + * @since 0.1.2 + */ + public JsonComparisonAssert hasSameContentAs(final File expectedFile) { + return hasSameContentAs(JsonNodeLoader.toNode(expectedFile, mapper)); } /** @@ -109,26 +154,20 @@ public JsonComparisonAssert hasSameContentAs(final Path expectedFile) { * @return this assertion object */ public JsonComparisonAssert hasSameContentAs(final String expectedJson) { - return hasSameContentAs(parseJson(expectedJson)); - } - - private JsonComparisonAssert hasSameContentAs(final JsonNode expected) { - JsonNode actualNode = actualJson(); - assertThat(actualNode).as("JSON content from %s", actual).isEqualTo(expected); - return this; + return hasSameContentAs(JsonNodeLoader.toNode(expectedJson, mapper)); } /** - * Verifies that the JSON document in the actual file contains the supplied JSON fragment. + * Verifies that the JSON document in the actual file contains the supplied JSON node. * Object fragments may omit fields; nested fragments are checked recursively. Arrays are * matched by position for the elements supplied in the fragment. * - * @param expectedFragment the JSON fragment to find + * @param expectedNode the JSON node to find * @return this assertion object + * @since 0.1.2 */ - public JsonComparisonAssert partiallyContains(final String expectedFragment) { - JsonNode actualNode = actualJson(); - JsonNode expectedNode = parseJson(expectedFragment); + public JsonComparisonAssert partiallyContains(final JsonNode expectedNode) { + final JsonNode actualNode = actualJson(); if (!contains(actualNode, expectedNode)) { failWithActualExpectedAndMessage( actualNode, expectedNode, "Expected JSON to partially contain"); @@ -136,6 +175,38 @@ public JsonComparisonAssert partiallyContains(final String expectedFragment) { return this; } + /** + * Verifies that the JSON document in the actual file contains the supplied JSON fragment string. + * + * @param expectedFragment the JSON fragment to find + * @return this assertion object + */ + public JsonComparisonAssert partiallyContains(final String expectedFragment) { + return partiallyContains(JsonNodeLoader.toNode(expectedFragment, mapper)); + } + + /** + * Verifies that the JSON document in the actual file contains the supplied JSON file content. + * + * @param expectedFile the JSON file to find + * @return this assertion object + * @since 0.1.2 + */ + public JsonComparisonAssert partiallyContains(final Path expectedFile) { + return partiallyContains(JsonNodeLoader.toNode(expectedFile, mapper)); + } + + /** + * Verifies that the JSON document in the actual file contains the supplied JSON file content. + * + * @param expectedFile the JSON file to find + * @return this assertion object + * @since 0.1.2 + */ + public JsonComparisonAssert partiallyContains(final File expectedFile) { + return partiallyContains(JsonNodeLoader.toNode(expectedFile, mapper)); + } + private boolean sameFields(final JsonNode actualNode, final JsonNode expectedNode) { if (actualNode.isObject() && expectedNode.isObject()) { if (actualNode.size() != expectedNode.size()) { diff --git a/src/main/java/com/archyoshi/assertj/json/api/JsonIterableAssert.java b/src/main/java/com/archyoshi/assertj/json/api/JsonIterableAssert.java index 4c7e5a7..b2ea4c4 100644 --- a/src/main/java/com/archyoshi/assertj/json/api/JsonIterableAssert.java +++ b/src/main/java/com/archyoshi/assertj/json/api/JsonIterableAssert.java @@ -16,6 +16,9 @@ package com.archyoshi.assertj.json.api; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.File; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -68,6 +71,77 @@ public static JsonIterableAssert assertThat(final Collection actual) { return new JsonIterableAssert(new ArrayList<>(actual)); } + private static final ObjectMapper DEFAULT_MAPPER = new ObjectMapper(); + + /** + * Creates assertions for a JSON array string using the default object mapper. + * + * @param jsonArray the JSON array string + * @return an assertion object for its elements + * @since 0.1.2 + */ + public static JsonIterableAssert assertThat(final String jsonArray) { + return assertThat(jsonArray, DEFAULT_MAPPER); + } + + /** + * Creates assertions for a JSON array string using the supplied object mapper. + * + * @param jsonArray the JSON array string + * @param mapper the mapper used to parse the string + * @return an assertion object for its elements + * @since 0.1.2 + */ + public static JsonIterableAssert assertThat(final String jsonArray, final ObjectMapper mapper) { + return assertThat(JsonNodeLoader.toNode(jsonArray, mapper)); + } + + /** + * Creates assertions for a JSON array file at the given path using the default object mapper. + * + * @param jsonArrayPath the path to the JSON array file + * @return an assertion object for its elements + * @since 0.1.2 + */ + public static JsonIterableAssert assertThat(final Path jsonArrayPath) { + return assertThat(jsonArrayPath, DEFAULT_MAPPER); + } + + /** + * Creates assertions for a JSON array file at the given path using the supplied mapper. + * + * @param jsonArrayPath the path to the JSON array file + * @param mapper the mapper used to parse JSON + * @return an assertion object for its elements + * @since 0.1.2 + */ + public static JsonIterableAssert assertThat(final Path jsonArrayPath, final ObjectMapper mapper) { + return assertThat(JsonNodeLoader.toNode(jsonArrayPath, mapper)); + } + + /** + * Creates assertions for a JSON array file using the default object mapper. + * + * @param jsonArrayFile the JSON array file + * @return an assertion object for its elements + * @since 0.1.2 + */ + public static JsonIterableAssert assertThat(final File jsonArrayFile) { + return assertThat(jsonArrayFile, DEFAULT_MAPPER); + } + + /** + * Creates assertions for a JSON array file using the supplied mapper. + * + * @param jsonArrayFile the JSON array file + * @param mapper the mapper used to parse JSON + * @return an assertion object for its elements + * @since 0.1.2 + */ + public static JsonIterableAssert assertThat(final File jsonArrayFile, final ObjectMapper mapper) { + return assertThat(JsonNodeLoader.toNode(jsonArrayFile, mapper)); + } + /** * Verifies that at least one object element has the given field. * diff --git a/src/main/java/com/archyoshi/assertj/json/api/JsonNodeAssert.java b/src/main/java/com/archyoshi/assertj/json/api/JsonNodeAssert.java index b6d2db8..2ea2e17 100644 --- a/src/main/java/com/archyoshi/assertj/json/api/JsonNodeAssert.java +++ b/src/main/java/com/archyoshi/assertj/json/api/JsonNodeAssert.java @@ -21,6 +21,10 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.JsonNodeType; +import java.io.File; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -267,6 +271,20 @@ private JsonNodeAssert hasNumericValueForField( return this; } + /** + * Verifies that the actual JSON content is equal to the expected JSON node. + * + * @param expectedNode the expected JSON node + * @return {@code this} assertion object + * @throws AssertionError if the actual JSON is not equal to the expected JSON + * @since 0.1.2 + */ + public JsonNodeAssert hasJsonContent(final JsonNode expectedNode) { + final JsonNode actualNode = actual; + assertThat(actualNode).isEqualTo(expectedNode); + return this; + } + /** * Verifies that the actual JSON content is equal to the expected JSON string. * @@ -291,10 +309,31 @@ private JsonNodeAssert hasNumericValueForField( * @since 0.1.0 */ public JsonNodeAssert hasJsonContent(final String expectedJson) { - final JsonNode actualNode = actual; - final JsonNode expectedNode = parseJson(expectedJson); - assertThat(actualNode).isEqualTo(expectedNode); - return this; + return hasJsonContent(JsonNodeLoader.toNode(expectedJson, mapper)); + } + + /** + * Verifies that the actual JSON content is equal to the JSON content in the specified path. + * + * @param expectedPath the path to the expected JSON file + * @return {@code this} assertion object + * @throws AssertionError if the file cannot be read or is not equal to the expected JSON + * @since 0.1.2 + */ + public JsonNodeAssert hasJsonContent(final Path expectedPath) { + return hasJsonContent(JsonNodeLoader.toNode(expectedPath, mapper)); + } + + /** + * Verifies that the actual JSON content is equal to the JSON content in the specified file. + * + * @param expectedFile the expected JSON file + * @return {@code this} assertion object + * @throws AssertionError if the file cannot be read or is not equal to the expected JSON + * @since 0.1.2 + */ + public JsonNodeAssert hasJsonContent(final File expectedFile) { + return hasJsonContent(JsonNodeLoader.toNode(expectedFile, mapper)); } /** @@ -303,6 +342,7 @@ public JsonNodeAssert hasJsonContent(final String expectedJson) { * @param expected the expected JSON node * @param ignoredFields field names to ignore at every object level * @return {@code this} assertion object + * @since 0.1.0 */ public JsonNodeAssert isEqualToIgnoringFields( final JsonNode expected, final List ignoredFields) { @@ -315,6 +355,111 @@ public JsonNodeAssert isEqualToIgnoringFields( return this; } + /** + * Verifies structural JSON equality while ignoring named fields recursively. + * + * @param expected the expected JSON node + * @param ignoredFields field names to ignore at every object level + * @return {@code this} assertion object + * @since 0.1.2 + */ + public JsonNodeAssert isEqualToIgnoringFields( + final JsonNode expected, final String... ignoredFields) { + return isEqualToIgnoringFields( + expected, + ignoredFields != null ? Arrays.asList(ignoredFields) : Collections.emptyList()); + } + + /** + * Verifies structural JSON equality with an expected JSON string while ignoring named fields + * recursively. + * + * @param expectedJson the expected JSON string + * @param ignoredFields field names to ignore at every object level + * @return {@code this} assertion object + * @since 0.1.2 + */ + public JsonNodeAssert isEqualToIgnoringFields( + final String expectedJson, final List ignoredFields) { + return isEqualToIgnoringFields(JsonNodeLoader.toNode(expectedJson, mapper), ignoredFields); + } + + /** + * Verifies structural JSON equality with an expected JSON string while ignoring named fields + * recursively. + * + * @param expectedJson the expected JSON string + * @param ignoredFields field names to ignore at every object level + * @return {@code this} assertion object + * @since 0.1.2 + */ + public JsonNodeAssert isEqualToIgnoringFields( + final String expectedJson, final String... ignoredFields) { + return isEqualToIgnoringFields( + expectedJson, + ignoredFields != null ? Arrays.asList(ignoredFields) : Collections.emptyList()); + } + + /** + * Verifies structural JSON equality with an expected JSON path while ignoring named fields + * recursively. + * + * @param expectedPath the path to the expected JSON file + * @param ignoredFields field names to ignore at every object level + * @return {@code this} assertion object + * @since 0.1.2 + */ + public JsonNodeAssert isEqualToIgnoringFields( + final Path expectedPath, final List ignoredFields) { + return isEqualToIgnoringFields(JsonNodeLoader.toNode(expectedPath, mapper), ignoredFields); + } + + /** + * Verifies structural JSON equality with an expected JSON path while ignoring named fields + * recursively. + * + * @param expectedPath the path to the expected JSON file + * @param ignoredFields field names to ignore at every object level + * @return {@code this} assertion object + * @since 0.1.2 + */ + public JsonNodeAssert isEqualToIgnoringFields( + final Path expectedPath, final String... ignoredFields) { + return isEqualToIgnoringFields( + expectedPath, + ignoredFields != null ? Arrays.asList(ignoredFields) : Collections.emptyList()); + } + + /** + * Verifies structural JSON equality with an expected JSON file while ignoring named fields + * recursively. + * + * @param expectedFile the expected JSON file + * @param ignoredFields field names to ignore at every object level + * @return {@code this} assertion object + * @since 0.1.2 + */ + public JsonNodeAssert isEqualToIgnoringFields( + final File expectedFile, final List ignoredFields) { + return isEqualToIgnoringFields(JsonNodeLoader.toNode(expectedFile, mapper), ignoredFields); + } + + /** + * Verifies structural JSON equality with an expected JSON file while ignoring named fields + * recursively. + * + * @param expectedFile the expected JSON file + * @param ignoredFields field names to ignore at every object level + * @return {@code this} assertion object + * @since 0.1.2 + */ + public JsonNodeAssert isEqualToIgnoringFields( + final File expectedFile, final String... ignoredFields) { + return isEqualToIgnoringFields( + expectedFile, + ignoredFields != null ? Arrays.asList(ignoredFields) : Collections.emptyList()); + } + private boolean equalsIgnoringFields( final JsonNode actualNode, final JsonNode expectedNode, diff --git a/src/main/java/com/archyoshi/assertj/json/api/JsonNodeLoader.java b/src/main/java/com/archyoshi/assertj/json/api/JsonNodeLoader.java new file mode 100644 index 0000000..37f394c --- /dev/null +++ b/src/main/java/com/archyoshi/assertj/json/api/JsonNodeLoader.java @@ -0,0 +1,101 @@ +/* + * Copyright (C)$YEAR the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.archyoshi.assertj.json.api; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +/** + * Adapter utility for converting various JSON sources (strings, files, paths) into {@link JsonNode} + * instances. + * + * @author archyoshi + * @since 0.1.2 + */ +public final class JsonNodeLoader { + + private JsonNodeLoader() { + throw new UnsupportedOperationException("Utility class should not be instantiated"); + } + + /** + * Returns the given {@link JsonNode} or throws an {@link AssertionError} if it is null. + * + * @param node the JSON node + * @return the same JSON node + */ + public static JsonNode toNode(final JsonNode node) { + if (node == null) { + throw new AssertionError("Expected JSON node not to be null"); + } + return node; + } + + /** + * Parses a JSON string into a {@link JsonNode} using the given mapper. + * + * @param json the JSON string + * @param mapper the mapper to use + * @return the parsed JSON node + */ + public static JsonNode toNode(final String json, final ObjectMapper mapper) { + if (json == null) { + throw new AssertionError("Expected JSON string not to be null"); + } + try { + return mapper.readTree(json); + } catch (final JsonProcessingException e) { + throw new AssertionError("Invalid JSON content: " + e.getOriginalMessage(), e); + } + } + + /** + * Reads and parses a JSON file from a {@link Path} using the given mapper. + * + * @param path the path to the JSON file + * @param mapper the mapper to use + * @return the parsed JSON node + */ + public static JsonNode toNode(final Path path, final ObjectMapper mapper) { + if (path == null) { + throw new AssertionError("Expected JSON path not to be null"); + } + try { + return toNode(Files.readString(path), mapper); + } catch (final IOException e) { + throw new AssertionError("Unable to read JSON file: " + path, e); + } + } + + /** + * Reads and parses a JSON file from a {@link File} using the given mapper. + * + * @param file the JSON file + * @param mapper the mapper to use + * @return the parsed JSON node + */ + public static JsonNode toNode(final File file, final ObjectMapper mapper) { + if (file == null) { + throw new AssertionError("Expected JSON file not to be null"); + } + return toNode(file.toPath(), mapper); + } +} diff --git a/src/test/java/com/archyoshi/assertj/json/JsonAssertionsTest.java b/src/test/java/com/archyoshi/assertj/json/JsonAssertionsTest.java index 606d11e..f3f2c0c 100644 --- a/src/test/java/com/archyoshi/assertj/json/JsonAssertionsTest.java +++ b/src/test/java/com/archyoshi/assertj/json/JsonAssertionsTest.java @@ -16,9 +16,11 @@ package com.archyoshi.assertj.json; import static com.archyoshi.assertj.json.JsonAssertions.assertThat; +import static com.archyoshi.assertj.json.JsonAssertions.assertThatArray; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; +import java.io.File; import java.nio.file.Files; import java.nio.file.Path; import org.junit.jupiter.api.Test; @@ -78,4 +80,33 @@ void shouldUseProvidedObjectMapperForFileParsing() throws Exception { assertThat(actual, mapper).hasSameContentAs(expected); } + + @Test + void shouldAssertOnFileDirectly() throws Exception { + final Path actual = Files.createTempFile("assertj-json-", ".json"); + Files.writeString(actual, "{\"name\":\"Vegeta\"}"); + final File file = actual.toFile(); + + assertThat(file).hasSameFieldsAs("{\"name\":\"Goku\"}"); + } + + @Test + void shouldAssertOnJsonArrayFromStringPathAndFile() throws Exception { + final String jsonArray = "[{\"name\":\"Vegeta\"},{\"name\":\"Goku\"}]"; + final Path arrayPath = Files.createTempFile("assertj-json-arr-", ".json"); + Files.writeString(arrayPath, jsonArray); + final File arrayFile = arrayPath.toFile(); + + assertThatArray(jsonArray) + .containsElementWithFieldName("name") + .containsElementWithFieldAndValue("name", "Vegeta"); + + assertThatArray(arrayPath) + .containsElementWithFieldName("name") + .containsElementWithFieldAndValue("name", "Goku"); + + assertThatArray(arrayFile) + .containsElementWithFieldName("name") + .containsElementWithFieldAndValue("name", "Vegeta"); + } } diff --git a/src/test/java/com/archyoshi/assertj/json/api/JsonComparisonAssertTest.java b/src/test/java/com/archyoshi/assertj/json/api/JsonComparisonAssertTest.java index e3f6d7a..d84ff1d 100644 --- a/src/test/java/com/archyoshi/assertj/json/api/JsonComparisonAssertTest.java +++ b/src/test/java/com/archyoshi/assertj/json/api/JsonComparisonAssertTest.java @@ -18,6 +18,9 @@ import static com.archyoshi.assertj.json.JsonAssertions.assertThat; import static org.assertj.core.api.BDDAssertions.thenThrownBy; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.File; import java.nio.file.Files; import java.nio.file.Path; import org.junit.jupiter.api.Test; @@ -72,6 +75,23 @@ void shouldMatchPartialObjectAndArrayContent() throws Exception { .partiallyContains("{\"profile\":{\"planet\":\"Vegeta\"},\"items\":[{\"id\":1}]}"); } + @Test + void shouldSupportJsonNodeAndFileForComparisons() throws Exception { + Path actual = jsonFile("{\"name\":\"Vegeta\",\"age\":30}"); + Path expectedPath = jsonFile("{\"age\":30,\"name\":\"Vegeta\"}"); + File expectedFile = expectedPath.toFile(); + JsonNode expectedNode = new ObjectMapper().readTree("{\"age\":30,\"name\":\"Vegeta\"}"); + + assertThat(actual) + .hasSameFieldsAs(expectedNode) + .hasSameFieldsAs(expectedFile) + .hasSameContentAs(expectedNode) + .hasSameContentAs(expectedFile) + .partiallyContains(expectedNode) + .partiallyContains(expectedPath) + .partiallyContains(expectedFile); + } + @Test void shouldFailWhenPartialContentIsMissing() throws Exception { Path actual = jsonFile("{\"name\":\"Vegeta\"}"); diff --git a/src/test/java/com/archyoshi/assertj/json/api/JsonNodeAssertAdditionalTest.java b/src/test/java/com/archyoshi/assertj/json/api/JsonNodeAssertAdditionalTest.java index 720db12..765d34f 100644 --- a/src/test/java/com/archyoshi/assertj/json/api/JsonNodeAssertAdditionalTest.java +++ b/src/test/java/com/archyoshi/assertj/json/api/JsonNodeAssertAdditionalTest.java @@ -22,9 +22,13 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.JsonNodeType; +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; /** * @author archyoshi @@ -79,6 +83,8 @@ void shouldFailForInvalidFieldAssertions() { .isInstanceOf(AssertionError.class); } + @TempDir Path tempDir; + @Test void shouldCompareWhileIgnoringNestedFields() throws JsonProcessingException { final JsonNode expected = @@ -90,10 +96,54 @@ void shouldCompareWhileIgnoringNestedFields() throws JsonProcessingException { """); assertThat(actual).isEqualToIgnoringFields(expected, List.of("active", "planet")); + assertThat(actual).isEqualToIgnoringFields(expected, "active", "planet"); thenThrownBy(() -> assertThat(actual).isEqualToIgnoringFields(expected, List.of("active"))) .isInstanceOf(AssertionError.class); } + @Test + void shouldCompareWhileIgnoringNestedFieldsUsingString() { + final String expectedJson = + """ + {"name":"Vegeta","age":30,"active":false,\ + "profile":{"planet":"Earth"},"items":[{"id":1},{"id":2}]}\ + """; + + assertThat(actual).isEqualToIgnoringFields(expectedJson, List.of("active", "planet")); + assertThat(actual).isEqualToIgnoringFields(expectedJson, "active", "planet"); + thenThrownBy(() -> assertThat(actual).isEqualToIgnoringFields(expectedJson, "active")) + .isInstanceOf(AssertionError.class); + } + + @Test + void shouldCompareWhileIgnoringNestedFieldsUsingPathAndFile() throws Exception { + final String expectedJson = + """ + {"name":"Vegeta","age":30,"active":false,\ + "profile":{"planet":"Earth"},"items":[{"id":1},{"id":2}]}\ + """; + final Path path = Files.writeString(Files.createTempFile(tempDir, "expected-", ".json"), expectedJson); + final File file = path.toFile(); + + assertThat(actual).isEqualToIgnoringFields(path, List.of("active", "planet")); + assertThat(actual).isEqualToIgnoringFields(path, "active", "planet"); + assertThat(actual).isEqualToIgnoringFields(file, List.of("active", "planet")); + assertThat(actual).isEqualToIgnoringFields(file, "active", "planet"); + } + + @Test + void shouldAssertHasJsonContentWithNodePathAndFile() throws Exception { + final String json = "{\"name\":\"Vegeta\",\"age\":30,\"active\":true,\"profile\":{\"planet\":\"Vegeta\"},\"items\":[{\"id\":1},{\"id\":2}]}"; + final JsonNode expectedNode = new ObjectMapper().readTree(json); + final Path expectedPath = Files.writeString(Files.createTempFile(tempDir, "content-", ".json"), json); + final File expectedFile = expectedPath.toFile(); + + assertThat(actual).hasJsonContent(expectedNode); + assertThat(actual).hasJsonContent(json); + assertThat(actual).hasJsonContent(expectedPath); + assertThat(actual).hasJsonContent(expectedFile); + } + @Test void shouldExtractNestedNodesAndArrays() { assertThat(actual) From 082eb978b8e1015ae5178106fdcd3552ce724786 Mon Sep 17 00:00:00 2001 From: Thami Inaflas Date: Mon, 7 Sep 2026 00:17:11 +0200 Subject: [PATCH 5/6] feat: --- .../assertj/json/JsonAssertions.java | 22 +++++++-------- .../json/api/JsonComparisonAssert.java | 27 ++++++++++--------- .../assertj/json/api/JsonIterableAssert.java | 18 +++++++------ .../assertj/json/api/JsonNodeAssert.java | 20 +++++++------- .../assertj/json/api/JsonNodeLoader.java | 4 +-- .../json/api/JsonIterableAssertTest.java | 2 +- .../api/JsonNodeAssertAdditionalTest.java | 10 ++++--- 7 files changed, 55 insertions(+), 48 deletions(-) diff --git a/src/main/java/com/archyoshi/assertj/json/JsonAssertions.java b/src/main/java/com/archyoshi/assertj/json/JsonAssertions.java index 1eefb4e..7c850d6 100644 --- a/src/main/java/com/archyoshi/assertj/json/JsonAssertions.java +++ b/src/main/java/com/archyoshi/assertj/json/JsonAssertions.java @@ -19,7 +19,6 @@ import com.archyoshi.assertj.json.api.JsonIterableAssert; import com.archyoshi.assertj.json.api.JsonNodeAssert; import com.archyoshi.assertj.json.api.JsonNodeLoader; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.File; @@ -85,7 +84,7 @@ public static JsonNodeAssert assertThat(final String json) { * @return a new {@link JsonNodeAssert} instance */ public static JsonNodeAssert assertThat(final String json, final ObjectMapper mapper) { - return new JsonNodeAssert(parseJson(json, mapper), mapper); + return new JsonNodeAssert(JsonNodeLoader.toNode(json, mapper), mapper); } /** @@ -121,7 +120,7 @@ public static JsonComparisonAssert assertThat(final Path jsonFile, final ObjectM * @param jsonFile the JSON file to assert on * @return a new {@link JsonComparisonAssert} instance * @throws AssertionError if the file cannot be read or contains invalid JSON - * @since 0.1.2 + * @since 0.1.3 */ public static JsonComparisonAssert assertThat(final File jsonFile) { return assertThat(jsonFile, MAPPER); @@ -133,7 +132,7 @@ public static JsonComparisonAssert assertThat(final File jsonFile) { * @param jsonFile the JSON file to assert on * @param mapper the mapper used to parse JSON * @return a new {@link JsonComparisonAssert} instance - * @since 0.1.2 + * @since 0.1.3 */ public static JsonComparisonAssert assertThat(final File jsonFile, final ObjectMapper mapper) { if (jsonFile == null) { @@ -160,7 +159,7 @@ public static JsonIterableAssert assertThatArray(final JsonNode actual) { * @param jsonArray the JSON array string * @return an assertion object for the array elements * @throws AssertionError if the string cannot be parsed or is not a JSON array - * @since 0.1.2 + * @since 0.1.3 */ public static JsonIterableAssert assertThatArray(final String jsonArray) { return assertThatArray(jsonArray, MAPPER); @@ -173,7 +172,7 @@ public static JsonIterableAssert assertThatArray(final String jsonArray) { * @param mapper the mapper used to parse the string * @return an assertion object for the array elements * @throws AssertionError if the string cannot be parsed or is not a JSON array - * @since 0.1.2 + * @since 0.1.3 */ public static JsonIterableAssert assertThatArray( final String jsonArray, final ObjectMapper mapper) { @@ -186,20 +185,21 @@ public static JsonIterableAssert assertThatArray( * @param jsonArrayPath the path to the JSON array file * @return an assertion object for the array elements * @throws AssertionError if the file cannot be read or is not a JSON array - * @since 0.1.2 + * @since 0.1.3 */ public static JsonIterableAssert assertThatArray(final Path jsonArrayPath) { return assertThatArray(jsonArrayPath, MAPPER); } /** - * Creates iterable assertions for a JSON array file at the given path using the supplied mapper. + * Creates iterable assertions for a JSON array file at the given path using the supplied + * mapper. * * @param jsonArrayPath the path to the JSON array file * @param mapper the mapper used to parse JSON * @return an assertion object for the array elements * @throws AssertionError if the file cannot be read or is not a JSON array - * @since 0.1.2 + * @since 0.1.3 */ public static JsonIterableAssert assertThatArray( final Path jsonArrayPath, final ObjectMapper mapper) { @@ -212,7 +212,7 @@ public static JsonIterableAssert assertThatArray( * @param jsonArrayFile the JSON array file * @return an assertion object for the array elements * @throws AssertionError if the file cannot be read or is not a JSON array - * @since 0.1.2 + * @since 0.1.3 */ public static JsonIterableAssert assertThatArray(final File jsonArrayFile) { return assertThatArray(jsonArrayFile, MAPPER); @@ -225,7 +225,7 @@ public static JsonIterableAssert assertThatArray(final File jsonArrayFile) { * @param mapper the mapper used to parse JSON * @return an assertion object for the array elements * @throws AssertionError if the file cannot be read or is not a JSON array - * @since 0.1.2 + * @since 0.1.3 */ public static JsonIterableAssert assertThatArray( final File jsonArrayFile, final ObjectMapper mapper) { diff --git a/src/main/java/com/archyoshi/assertj/json/api/JsonComparisonAssert.java b/src/main/java/com/archyoshi/assertj/json/api/JsonComparisonAssert.java index 34ff53f..fa2b2b0 100644 --- a/src/main/java/com/archyoshi/assertj/json/api/JsonComparisonAssert.java +++ b/src/main/java/com/archyoshi/assertj/json/api/JsonComparisonAssert.java @@ -63,12 +63,12 @@ public JsonComparisonAssert(final Path actual, final ObjectMapper mapper) { } /** - * Verifies that the actual JSON file has the same field names as the expected JSON node. - * Values are ignored, while nested object structure and array positions are preserved. + * Verifies that the actual JSON file has the same field names as the expected JSON node. Values + * are ignored, while nested object structure and array positions are preserved. * * @param expected the expected JSON node * @return this assertion object - * @since 0.1.2 + * @since 0.1.3 */ public JsonComparisonAssert hasSameFieldsAs(final JsonNode expected) { final JsonNode actualNode = actualJson(); @@ -95,7 +95,7 @@ public JsonComparisonAssert hasSameFieldsAs(final Path expectedFile) { * * @param expectedFile the expected JSON file * @return this assertion object - * @since 0.1.2 + * @since 0.1.3 */ public JsonComparisonAssert hasSameFieldsAs(final File expectedFile) { return hasSameFieldsAs(JsonNodeLoader.toNode(expectedFile, mapper)); @@ -117,7 +117,7 @@ public JsonComparisonAssert hasSameFieldsAs(final String expectedJson) { * * @param expected the expected JSON node * @return this assertion object - * @since 0.1.2 + * @since 0.1.3 */ public JsonComparisonAssert hasSameContentAs(final JsonNode expected) { final JsonNode actualNode = actualJson(); @@ -141,7 +141,7 @@ public JsonComparisonAssert hasSameContentAs(final Path expectedFile) { * * @param expectedFile the expected JSON file * @return this assertion object - * @since 0.1.2 + * @since 0.1.3 */ public JsonComparisonAssert hasSameContentAs(final File expectedFile) { return hasSameContentAs(JsonNodeLoader.toNode(expectedFile, mapper)); @@ -158,13 +158,13 @@ public JsonComparisonAssert hasSameContentAs(final String expectedJson) { } /** - * Verifies that the JSON document in the actual file contains the supplied JSON node. - * Object fragments may omit fields; nested fragments are checked recursively. Arrays are - * matched by position for the elements supplied in the fragment. + * Verifies that the JSON document in the actual file contains the supplied JSON node. Object + * fragments may omit fields; nested fragments are checked recursively. Arrays are matched by + * position for the elements supplied in the fragment. * * @param expectedNode the JSON node to find * @return this assertion object - * @since 0.1.2 + * @since 0.1.3 */ public JsonComparisonAssert partiallyContains(final JsonNode expectedNode) { final JsonNode actualNode = actualJson(); @@ -176,7 +176,8 @@ public JsonComparisonAssert partiallyContains(final JsonNode expectedNode) { } /** - * Verifies that the JSON document in the actual file contains the supplied JSON fragment string. + * Verifies that the JSON document in the actual file contains the supplied JSON fragment + * string. * * @param expectedFragment the JSON fragment to find * @return this assertion object @@ -190,7 +191,7 @@ public JsonComparisonAssert partiallyContains(final String expectedFragment) { * * @param expectedFile the JSON file to find * @return this assertion object - * @since 0.1.2 + * @since 0.1.3 */ public JsonComparisonAssert partiallyContains(final Path expectedFile) { return partiallyContains(JsonNodeLoader.toNode(expectedFile, mapper)); @@ -201,7 +202,7 @@ public JsonComparisonAssert partiallyContains(final Path expectedFile) { * * @param expectedFile the JSON file to find * @return this assertion object - * @since 0.1.2 + * @since 0.1.3 */ public JsonComparisonAssert partiallyContains(final File expectedFile) { return partiallyContains(JsonNodeLoader.toNode(expectedFile, mapper)); diff --git a/src/main/java/com/archyoshi/assertj/json/api/JsonIterableAssert.java b/src/main/java/com/archyoshi/assertj/json/api/JsonIterableAssert.java index b2ea4c4..2975262 100644 --- a/src/main/java/com/archyoshi/assertj/json/api/JsonIterableAssert.java +++ b/src/main/java/com/archyoshi/assertj/json/api/JsonIterableAssert.java @@ -78,7 +78,7 @@ public static JsonIterableAssert assertThat(final Collection actual) { * * @param jsonArray the JSON array string * @return an assertion object for its elements - * @since 0.1.2 + * @since 0.1.3 */ public static JsonIterableAssert assertThat(final String jsonArray) { return assertThat(jsonArray, DEFAULT_MAPPER); @@ -90,7 +90,7 @@ public static JsonIterableAssert assertThat(final String jsonArray) { * @param jsonArray the JSON array string * @param mapper the mapper used to parse the string * @return an assertion object for its elements - * @since 0.1.2 + * @since 0.1.3 */ public static JsonIterableAssert assertThat(final String jsonArray, final ObjectMapper mapper) { return assertThat(JsonNodeLoader.toNode(jsonArray, mapper)); @@ -101,7 +101,7 @@ public static JsonIterableAssert assertThat(final String jsonArray, final Object * * @param jsonArrayPath the path to the JSON array file * @return an assertion object for its elements - * @since 0.1.2 + * @since 0.1.3 */ public static JsonIterableAssert assertThat(final Path jsonArrayPath) { return assertThat(jsonArrayPath, DEFAULT_MAPPER); @@ -113,9 +113,10 @@ public static JsonIterableAssert assertThat(final Path jsonArrayPath) { * @param jsonArrayPath the path to the JSON array file * @param mapper the mapper used to parse JSON * @return an assertion object for its elements - * @since 0.1.2 + * @since 0.1.3 */ - public static JsonIterableAssert assertThat(final Path jsonArrayPath, final ObjectMapper mapper) { + public static JsonIterableAssert assertThat( + final Path jsonArrayPath, final ObjectMapper mapper) { return assertThat(JsonNodeLoader.toNode(jsonArrayPath, mapper)); } @@ -124,7 +125,7 @@ public static JsonIterableAssert assertThat(final Path jsonArrayPath, final Obje * * @param jsonArrayFile the JSON array file * @return an assertion object for its elements - * @since 0.1.2 + * @since 0.1.3 */ public static JsonIterableAssert assertThat(final File jsonArrayFile) { return assertThat(jsonArrayFile, DEFAULT_MAPPER); @@ -136,9 +137,10 @@ public static JsonIterableAssert assertThat(final File jsonArrayFile) { * @param jsonArrayFile the JSON array file * @param mapper the mapper used to parse JSON * @return an assertion object for its elements - * @since 0.1.2 + * @since 0.1.3 */ - public static JsonIterableAssert assertThat(final File jsonArrayFile, final ObjectMapper mapper) { + public static JsonIterableAssert assertThat( + final File jsonArrayFile, final ObjectMapper mapper) { return assertThat(JsonNodeLoader.toNode(jsonArrayFile, mapper)); } diff --git a/src/main/java/com/archyoshi/assertj/json/api/JsonNodeAssert.java b/src/main/java/com/archyoshi/assertj/json/api/JsonNodeAssert.java index 2ea2e17..cd5acd4 100644 --- a/src/main/java/com/archyoshi/assertj/json/api/JsonNodeAssert.java +++ b/src/main/java/com/archyoshi/assertj/json/api/JsonNodeAssert.java @@ -277,7 +277,7 @@ private JsonNodeAssert hasNumericValueForField( * @param expectedNode the expected JSON node * @return {@code this} assertion object * @throws AssertionError if the actual JSON is not equal to the expected JSON - * @since 0.1.2 + * @since 0.1.3 */ public JsonNodeAssert hasJsonContent(final JsonNode expectedNode) { final JsonNode actualNode = actual; @@ -318,7 +318,7 @@ public JsonNodeAssert hasJsonContent(final String expectedJson) { * @param expectedPath the path to the expected JSON file * @return {@code this} assertion object * @throws AssertionError if the file cannot be read or is not equal to the expected JSON - * @since 0.1.2 + * @since 0.1.3 */ public JsonNodeAssert hasJsonContent(final Path expectedPath) { return hasJsonContent(JsonNodeLoader.toNode(expectedPath, mapper)); @@ -330,7 +330,7 @@ public JsonNodeAssert hasJsonContent(final Path expectedPath) { * @param expectedFile the expected JSON file * @return {@code this} assertion object * @throws AssertionError if the file cannot be read or is not equal to the expected JSON - * @since 0.1.2 + * @since 0.1.3 */ public JsonNodeAssert hasJsonContent(final File expectedFile) { return hasJsonContent(JsonNodeLoader.toNode(expectedFile, mapper)); @@ -361,7 +361,7 @@ public JsonNodeAssert isEqualToIgnoringFields( * @param expected the expected JSON node * @param ignoredFields field names to ignore at every object level * @return {@code this} assertion object - * @since 0.1.2 + * @since 0.1.3 */ public JsonNodeAssert isEqualToIgnoringFields( final JsonNode expected, final String... ignoredFields) { @@ -377,7 +377,7 @@ public JsonNodeAssert isEqualToIgnoringFields( * @param expectedJson the expected JSON string * @param ignoredFields field names to ignore at every object level * @return {@code this} assertion object - * @since 0.1.2 + * @since 0.1.3 */ public JsonNodeAssert isEqualToIgnoringFields( final String expectedJson, final List ignoredFields) { @@ -391,7 +391,7 @@ public JsonNodeAssert isEqualToIgnoringFields( * @param expectedJson the expected JSON string * @param ignoredFields field names to ignore at every object level * @return {@code this} assertion object - * @since 0.1.2 + * @since 0.1.3 */ public JsonNodeAssert isEqualToIgnoringFields( final String expectedJson, final String... ignoredFields) { @@ -407,7 +407,7 @@ public JsonNodeAssert isEqualToIgnoringFields( * @param expectedPath the path to the expected JSON file * @param ignoredFields field names to ignore at every object level * @return {@code this} assertion object - * @since 0.1.2 + * @since 0.1.3 */ public JsonNodeAssert isEqualToIgnoringFields( final Path expectedPath, final List ignoredFields) { @@ -421,7 +421,7 @@ public JsonNodeAssert isEqualToIgnoringFields( * @param expectedPath the path to the expected JSON file * @param ignoredFields field names to ignore at every object level * @return {@code this} assertion object - * @since 0.1.2 + * @since 0.1.3 */ public JsonNodeAssert isEqualToIgnoringFields( final Path expectedPath, final String... ignoredFields) { @@ -437,7 +437,7 @@ public JsonNodeAssert isEqualToIgnoringFields( * @param expectedFile the expected JSON file * @param ignoredFields field names to ignore at every object level * @return {@code this} assertion object - * @since 0.1.2 + * @since 0.1.3 */ public JsonNodeAssert isEqualToIgnoringFields( final File expectedFile, final List ignoredFields) { @@ -451,7 +451,7 @@ public JsonNodeAssert isEqualToIgnoringFields( * @param expectedFile the expected JSON file * @param ignoredFields field names to ignore at every object level * @return {@code this} assertion object - * @since 0.1.2 + * @since 0.1.3 */ public JsonNodeAssert isEqualToIgnoringFields( final File expectedFile, final String... ignoredFields) { diff --git a/src/main/java/com/archyoshi/assertj/json/api/JsonNodeLoader.java b/src/main/java/com/archyoshi/assertj/json/api/JsonNodeLoader.java index 37f394c..cba59e2 100644 --- a/src/main/java/com/archyoshi/assertj/json/api/JsonNodeLoader.java +++ b/src/main/java/com/archyoshi/assertj/json/api/JsonNodeLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (C)$YEAR the original author or authors. + * Copyright (C)2026 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,7 +28,7 @@ * instances. * * @author archyoshi - * @since 0.1.2 + * @since 0.1.3 */ public final class JsonNodeLoader { diff --git a/src/test/java/com/archyoshi/assertj/json/api/JsonIterableAssertTest.java b/src/test/java/com/archyoshi/assertj/json/api/JsonIterableAssertTest.java index 1bf63cf..62eac6f 100644 --- a/src/test/java/com/archyoshi/assertj/json/api/JsonIterableAssertTest.java +++ b/src/test/java/com/archyoshi/assertj/json/api/JsonIterableAssertTest.java @@ -92,7 +92,7 @@ void shouldFailWhenExtractingUnknownElement() { @Test void shouldRejectNullAndNonArrayNodes() throws Exception { - thenThrownBy(() -> assertThatArray(null)).isInstanceOf(AssertionError.class); + thenThrownBy(() -> assertThatArray((JsonNode) null)).isInstanceOf(AssertionError.class); thenThrownBy(() -> assertThatArray(mapper.readTree("{}"))) .isInstanceOf(AssertionError.class); } diff --git a/src/test/java/com/archyoshi/assertj/json/api/JsonNodeAssertAdditionalTest.java b/src/test/java/com/archyoshi/assertj/json/api/JsonNodeAssertAdditionalTest.java index 765d34f..caed455 100644 --- a/src/test/java/com/archyoshi/assertj/json/api/JsonNodeAssertAdditionalTest.java +++ b/src/test/java/com/archyoshi/assertj/json/api/JsonNodeAssertAdditionalTest.java @@ -122,7 +122,9 @@ void shouldCompareWhileIgnoringNestedFieldsUsingPathAndFile() throws Exception { {"name":"Vegeta","age":30,"active":false,\ "profile":{"planet":"Earth"},"items":[{"id":1},{"id":2}]}\ """; - final Path path = Files.writeString(Files.createTempFile(tempDir, "expected-", ".json"), expectedJson); + final Path path = + Files.writeString( + Files.createTempFile(tempDir, "expected-", ".json"), expectedJson); final File file = path.toFile(); assertThat(actual).isEqualToIgnoringFields(path, List.of("active", "planet")); @@ -133,9 +135,11 @@ void shouldCompareWhileIgnoringNestedFieldsUsingPathAndFile() throws Exception { @Test void shouldAssertHasJsonContentWithNodePathAndFile() throws Exception { - final String json = "{\"name\":\"Vegeta\",\"age\":30,\"active\":true,\"profile\":{\"planet\":\"Vegeta\"},\"items\":[{\"id\":1},{\"id\":2}]}"; + final String json = + "{\"name\":\"Vegeta\",\"age\":30,\"active\":true,\"profile\":{\"planet\":\"Vegeta\"},\"items\":[{\"id\":1},{\"id\":2}]}"; final JsonNode expectedNode = new ObjectMapper().readTree(json); - final Path expectedPath = Files.writeString(Files.createTempFile(tempDir, "content-", ".json"), json); + final Path expectedPath = + Files.writeString(Files.createTempFile(tempDir, "content-", ".json"), json); final File expectedFile = expectedPath.toFile(); assertThat(actual).hasJsonContent(expectedNode); From 431bc218817a5f89d9a21f0fb1f597e68b2da812 Mon Sep 17 00:00:00 2001 From: Thami Inaflas Date: Mon, 7 Sep 2026 01:27:35 +0200 Subject: [PATCH 6/6] feat: add field extraction methods returning AssertJ core assertions --- .../assertj/json/api/JsonNodeAssert.java | 128 ++++++++++++++++++ .../api/JsonNodeAssertAdditionalTest.java | 50 ++++++- 2 files changed, 173 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/archyoshi/assertj/json/api/JsonNodeAssert.java b/src/main/java/com/archyoshi/assertj/json/api/JsonNodeAssert.java index cd5acd4..452a2e3 100644 --- a/src/main/java/com/archyoshi/assertj/json/api/JsonNodeAssert.java +++ b/src/main/java/com/archyoshi/assertj/json/api/JsonNodeAssert.java @@ -30,6 +30,13 @@ import java.util.Map; import java.util.function.Predicate; import org.assertj.core.api.AbstractAssert; +import org.assertj.core.api.AbstractBigDecimalAssert; +import org.assertj.core.api.AbstractBooleanAssert; +import org.assertj.core.api.AbstractDoubleAssert; +import org.assertj.core.api.AbstractIntegerAssert; +import org.assertj.core.api.AbstractLongAssert; +import org.assertj.core.api.AbstractStringAssert; +import org.assertj.core.api.Assertions; import org.assertj.core.api.ThrowingConsumer; /** @@ -138,7 +145,10 @@ public JsonNodeAssert hasTypeForField(final String fieldName, final JsonNodeType * @param the target Java type * @return {@code this} assertion object * @since 0.1.0 + * @deprecated Use type-specific extraction methods like {@link #extractFieldAsString(String)} + * instead. */ + @Deprecated public JsonNodeAssert hasTypedValueForField( final T expectedValue, final String fieldName, final Class valueType) { final JsonNode node = actual; @@ -169,7 +179,9 @@ public JsonNodeAssert hasTypedValueForField( * @throws AssertionError if the actual JSON object is null * @throws AssertionError if the field does not exist or has a different value * @since 0.1.0 + * @deprecated Use {@link #extractFieldAsString(String)} instead. */ + @Deprecated public JsonNodeAssert hasValueForField(final String expectedValue, final String fieldName) { final JsonNode node = actual; hasField(fieldName); @@ -200,7 +212,9 @@ public JsonNodeAssert hasValueForField(final String expectedValue, final String * @throws AssertionError if the actual JSON object is null * @throws AssertionError if the field does not exist or has a different value * @since 0.1.0 + * @deprecated Use {@link #extractFieldAsInteger(String)} instead. */ + @Deprecated public JsonNodeAssert hasValueForField(final int expectedValue, final String fieldName) { final JsonNode node = actual; hasField(fieldName); @@ -216,7 +230,9 @@ public JsonNodeAssert hasValueForField(final int expectedValue, final String fie * @param fieldName the field name to verify * @return {@code this} assertion object * @since 0.1.0 + * @deprecated Use {@link #extractFieldAsInteger(String)} instead. */ + @Deprecated public JsonNodeAssert hasValueEqualForField(final int expectedValue, final String fieldName) { return hasNumericValueForField( expectedValue, fieldName, value -> value == expectedValue, "equal to"); @@ -229,7 +245,9 @@ public JsonNodeAssert hasValueEqualForField(final int expectedValue, final Strin * @param fieldName the field name to verify * @return {@code this} assertion object * @since 0.1.0 + * @deprecated Use {@link #extractFieldAsInteger(String)} instead. */ + @Deprecated public JsonNodeAssert hasValueMoreThanForField( final int expectedValue, final String fieldName) { return hasNumericValueForField( @@ -243,7 +261,9 @@ public JsonNodeAssert hasValueMoreThanForField( * @param fieldName the field name to verify * @return {@code this} assertion object * @since 0.1.0 + * @deprecated Use {@link #extractFieldAsInteger(String)} instead. */ + @Deprecated public JsonNodeAssert hasValueLessThanForField( final int expectedValue, final String fieldName) { return hasNumericValueForField( @@ -673,6 +693,114 @@ public JsonNodeAssert hasSizeForArrayField(final int expectedSize, final String return this; } + /** + * Extracts a child field as a String for standard AssertJ string assertions. + * + * @param fieldName the field name to extract + * @return a String assertion object + * @since 0.1.3 + */ + public AbstractStringAssert extractFieldAsString(final String fieldName) { + hasField(fieldName); + final JsonNode field = actual.get(fieldName); + if (!field.isTextual()) { + failWithMessage( + "Expected field '%s' to be a STRING but was <%s>", + fieldName, field.getNodeType()); + } + return Assertions.assertThat(field.asText()); + } + + /** + * Extracts a child field as an Integer for standard AssertJ integer assertions. + * + * @param fieldName the field name to extract + * @return an Integer assertion object + * @since 0.1.3 + */ + public AbstractIntegerAssert extractFieldAsInteger(final String fieldName) { + hasField(fieldName); + final JsonNode field = actual.get(fieldName); + if (!field.isInt() && !field.canConvertToInt()) { + failWithMessage( + "Expected field '%s' to be an INTEGER but was <%s>", + fieldName, field.getNodeType()); + } + return Assertions.assertThat(field.asInt()); + } + + /** + * Extracts a child field as a Long for standard AssertJ long assertions. + * + * @param fieldName the field name to extract + * @return a Long assertion object + * @since 0.1.3 + */ + public AbstractLongAssert extractFieldAsLong(final String fieldName) { + hasField(fieldName); + final JsonNode field = actual.get(fieldName); + if (!field.isLong() && !field.canConvertToLong()) { + failWithMessage( + "Expected field '%s' to be a LONG but was <%s>", + fieldName, field.getNodeType()); + } + return Assertions.assertThat(field.asLong()); + } + + /** + * Extracts a child field as a Double for standard AssertJ double assertions. + * + * @param fieldName the field name to extract + * @return a Double assertion object + * @since 0.1.3 + */ + public AbstractDoubleAssert extractFieldAsDouble(final String fieldName) { + hasField(fieldName); + final JsonNode field = actual.get(fieldName); + if (!field.isDouble() && !field.isNumber()) { + failWithMessage( + "Expected field '%s' to be a DOUBLE but was <%s>", + fieldName, field.getNodeType()); + } + return Assertions.assertThat(field.asDouble()); + } + + /** + * Extracts a child field as a Boolean for standard AssertJ boolean assertions. + * + * @param fieldName the field name to extract + * @return a Boolean assertion object + * @since 0.1.3 + */ + public AbstractBooleanAssert extractFieldAsBoolean(final String fieldName) { + hasField(fieldName); + final JsonNode field = actual.get(fieldName); + if (!field.isBoolean()) { + failWithMessage( + "Expected field '%s' to be a BOOLEAN but was <%s>", + fieldName, field.getNodeType()); + } + return Assertions.assertThat(field.asBoolean()); + } + + /** + * Extracts a child field as a BigDecimal for standard AssertJ BigDecimal assertions. + * + * @param fieldName the field name to extract + * @return a BigDecimal assertion object + * @since 0.1.3 + */ + public AbstractBigDecimalAssert extractFieldAsBigDecimal(final String fieldName) { + hasField(fieldName); + final JsonNode field = actual.get(fieldName); + if (!field.isNumber()) { + failWithMessage( + "Expected field '%s' to be a NUMBER but was <%s>", + fieldName, field.getNodeType()); + } + return Assertions.assertThat(field.decimalValue()); + } + /** * Extracts a child field for further JSON assertions. * diff --git a/src/test/java/com/archyoshi/assertj/json/api/JsonNodeAssertAdditionalTest.java b/src/test/java/com/archyoshi/assertj/json/api/JsonNodeAssertAdditionalTest.java index caed455..70b1e96 100644 --- a/src/test/java/com/archyoshi/assertj/json/api/JsonNodeAssertAdditionalTest.java +++ b/src/test/java/com/archyoshi/assertj/json/api/JsonNodeAssertAdditionalTest.java @@ -43,7 +43,7 @@ void setUp() throws JsonProcessingException { new ObjectMapper() .readTree( """ - {"name":"Vegeta","age":30,"active":true,\ + {"name":"Vegeta","age":30,"active":true,"score":9.5,"power":9000000000,\ "profile":{"planet":"Vegeta"},"items":[{"id":1},{"id":2}]}\ """); } @@ -91,7 +91,7 @@ void shouldCompareWhileIgnoringNestedFields() throws JsonProcessingException { new ObjectMapper() .readTree( """ - {"name":"Vegeta","age":30,"active":false,\ + {"name":"Vegeta","age":30,"active":false,"score":9.5,"power":9000000000,\ "profile":{"planet":"Earth"},"items":[{"id":1},{"id":2}]}\ """); @@ -105,7 +105,7 @@ void shouldCompareWhileIgnoringNestedFields() throws JsonProcessingException { void shouldCompareWhileIgnoringNestedFieldsUsingString() { final String expectedJson = """ - {"name":"Vegeta","age":30,"active":false,\ + {"name":"Vegeta","age":30,"active":false,"score":9.5,"power":9000000000,\ "profile":{"planet":"Earth"},"items":[{"id":1},{"id":2}]}\ """; @@ -119,7 +119,7 @@ void shouldCompareWhileIgnoringNestedFieldsUsingString() { void shouldCompareWhileIgnoringNestedFieldsUsingPathAndFile() throws Exception { final String expectedJson = """ - {"name":"Vegeta","age":30,"active":false,\ + {"name":"Vegeta","age":30,"active":false,"score":9.5,"power":9000000000,\ "profile":{"planet":"Earth"},"items":[{"id":1},{"id":2}]}\ """; final Path path = @@ -136,7 +136,7 @@ void shouldCompareWhileIgnoringNestedFieldsUsingPathAndFile() throws Exception { @Test void shouldAssertHasJsonContentWithNodePathAndFile() throws Exception { final String json = - "{\"name\":\"Vegeta\",\"age\":30,\"active\":true,\"profile\":{\"planet\":\"Vegeta\"},\"items\":[{\"id\":1},{\"id\":2}]}"; + "{\"name\":\"Vegeta\",\"age\":30,\"active\":true,\"score\":9.5,\"power\":9000000000,\"profile\":{\"planet\":\"Vegeta\"},\"items\":[{\"id\":1},{\"id\":2}]}"; final JsonNode expectedNode = new ObjectMapper().readTree(json); final Path expectedPath = Files.writeString(Files.createTempFile(tempDir, "content-", ".json"), json); @@ -167,4 +167,44 @@ void shouldApplyCustomNodeAssertions() { } }); } + + @Test + void shouldExtractFieldAsString() { + assertThat(actual).extractFieldAsString("name").startsWith("Veg").endsWith("eta"); + + thenThrownBy(() -> assertThat(actual).extractFieldAsString("age")) + .isInstanceOf(AssertionError.class) + .hasMessageContaining("Expected field 'age' to be a STRING"); + } + + @Test + void shouldExtractFieldAsInteger() { + assertThat(actual).extractFieldAsInteger("age").isGreaterThan(20).isLessThan(40); + + thenThrownBy(() -> assertThat(actual).extractFieldAsInteger("name")) + .isInstanceOf(AssertionError.class) + .hasMessageContaining("Expected field 'name' to be an INTEGER"); + } + + @Test + void shouldExtractFieldAsLong() { + assertThat(actual).extractFieldAsLong("power").isGreaterThan(8000000000L); + } + + @Test + void shouldExtractFieldAsDouble() { + assertThat(actual).extractFieldAsDouble("score").isBetween(9.0, 10.0); + } + + @Test + void shouldExtractFieldAsBoolean() { + assertThat(actual).extractFieldAsBoolean("active").isTrue(); + } + + @Test + void shouldExtractFieldAsBigDecimal() { + assertThat(actual) + .extractFieldAsBigDecimal("score") + .isGreaterThan(java.math.BigDecimal.valueOf(9.0)); + } }