diff --git a/pom.xml b/pom.xml
index 7f9e00a..2fc91a8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
com.archyoshi
assertj-json
- 0.1.2
+ 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..7c850d6 100644
--- a/src/main/java/com/archyoshi/assertj/json/JsonAssertions.java
+++ b/src/main/java/com/archyoshi/assertj/json/JsonAssertions.java
@@ -18,9 +18,10 @@
import com.archyoshi.assertj.json.api.JsonComparisonAssert;
import com.archyoshi.assertj.json.api.JsonIterableAssert;
import com.archyoshi.assertj.json.api.JsonNodeAssert;
-import com.fasterxml.jackson.core.JsonProcessingException;
+import com.archyoshi.assertj.json.api.JsonNodeLoader;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
+import java.io.File;
import java.nio.file.Path;
/**
@@ -83,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);
}
/**
@@ -92,7 +93,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 +112,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.3
+ */
+ 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.3
+ */
+ 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 +153,82 @@ 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.3
+ */
+ 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.3
+ */
+ 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.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.
+ *
+ * @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.3
+ */
+ 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.3
+ */
+ 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.3
+ */
+ 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..fa2b2b0 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.3
+ */
+ 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.3
+ */
+ 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.3
+ */
+ 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.3
+ */
+ 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.
- * 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 expectedFragment the JSON fragment to find
+ * @param expectedNode the JSON node to find
* @return this assertion object
+ * @since 0.1.3
*/
- 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,39 @@ 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.3
+ */
+ 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.3
+ */
+ 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..2975262 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,79 @@ 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.3
+ */
+ 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.3
+ */
+ 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.3
+ */
+ 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.3
+ */
+ 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.3
+ */
+ 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.3
+ */
+ 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..452a2e3 100644
--- a/src/main/java/com/archyoshi/assertj/json/api/JsonNodeAssert.java
+++ b/src/main/java/com/archyoshi/assertj/json/api/JsonNodeAssert.java
@@ -21,11 +21,22 @@
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;
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;
/**
@@ -134,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;
@@ -165,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);
@@ -196,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);
@@ -212,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");
@@ -225,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(
@@ -239,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(
@@ -267,6 +291,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.3
+ */
+ 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 +329,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.3
+ */
+ 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.3
+ */
+ public JsonNodeAssert hasJsonContent(final File expectedFile) {
+ return hasJsonContent(JsonNodeLoader.toNode(expectedFile, mapper));
}
/**
@@ -303,6 +362,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 +375,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.3
+ */
+ 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.3
+ */
+ 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.3
+ */
+ 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.3
+ */
+ 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.3
+ */
+ 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.3
+ */
+ 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.3
+ */
+ 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,
@@ -528,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/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..cba59e2
--- /dev/null
+++ b/src/main/java/com/archyoshi/assertj/json/api/JsonNodeLoader.java
@@ -0,0 +1,101 @@
+/*
+ * 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.
+ * 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.3
+ */
+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/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 720db12..70b1e96 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
@@ -39,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}]}\
""");
}
@@ -79,21 +83,71 @@ void shouldFailForInvalidFieldAssertions() {
.isInstanceOf(AssertionError.class);
}
+ @TempDir Path tempDir;
+
@Test
void shouldCompareWhileIgnoringNestedFields() throws JsonProcessingException {
final JsonNode expected =
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}]}\
""");
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,"score":9.5,"power":9000000000,\
+ "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,"score":9.5,"power":9000000000,\
+ "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,\"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);
+ 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)
@@ -113,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));
+ }
}