Comprehensive JSON testing framework with multiple library support (Jackson 3, Jackson 2, Gson, JSON-B) and rich AssertJ-based assertions.
org.springframework.boot.test.json
// Jackson 3 (recommended)
import org.springframework.boot.test.json.JacksonTester;
import tools.jackson.databind.json.JsonMapper;
// Jackson 2 (deprecated)
import org.springframework.boot.test.json.Jackson2Tester;
import com.fasterxml.jackson.databind.ObjectMapper;
// Gson
import org.springframework.boot.test.json.GsonTester;
import com.google.gson.Gson;
// JSON-B
import org.springframework.boot.test.json.JsonbTester;
import jakarta.json.bind.Jsonb;
// Common
import org.springframework.boot.test.json.JsonContent;
import org.springframework.boot.test.json.ObjectContent;
import org.springframework.boot.test.json.BasicJsonTester;
import org.springframework.core.ResolvableType;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;Full Package: org.springframework.boot.test.json.JacksonTester<T>
Since: 1.4.0 (updated for Jackson 3 in 4.0.0)
Jackson 3 (tools.jackson) JSON tester with comprehensive serialization and deserialization support.
/**
* AssertJ-based JSON tester backed by Jackson 3
* Provides write, read, and parse operations with multiple input formats
* @param <T> the type under test
* @since 1.4.0 (updated for Jackson 3 in 4.0.0)
*/
public class JacksonTester<T> extends AbstractJsonMarshalTester<T> {
// ===== Constructors =====
/**
* Create a new uninitialized JacksonTester instance
* Used internally for subclassing and advanced scenarios
* @param jsonMapper the Jackson JSON mapper
* @since 4.0.0
*/
protected JacksonTester(JsonMapper jsonMapper);
/**
* Create a new JacksonTester instance
* Rarely used directly - prefer initFields() for automatic initialization
* @param resourceLoadClass the source class used to load resources
* @param type the type under test (use ResolvableType for generics)
* @param jsonMapper the Jackson JSON mapper
* @since 4.0.0
*/
public JacksonTester(Class<?> resourceLoadClass, ResolvableType type, JsonMapper jsonMapper);
/**
* Create a new JacksonTester instance with JSON view support
* @param resourceLoadClass the source class used to load resources
* @param type the type under test
* @param jsonMapper the Jackson JSON mapper
* @param view the JSON view class (for @JsonView filtering)
*/
public JacksonTester(Class<?> resourceLoadClass, ResolvableType type, JsonMapper jsonMapper, Class<?> view);
// ===== Static Initialization (Preferred Method) =====
/**
* Initialize all JacksonTester fields in a test instance via reflection
* Scans for all JacksonTester fields and initializes them automatically
* PREFERRED METHOD for setting up JSON testers
* @param testInstance the test instance containing JacksonTester fields
* @param jsonMapper the Jackson JSON mapper to use for all fields
*/
public static void initFields(Object testInstance, JsonMapper jsonMapper);
/**
* Initialize all JacksonTester fields using a mapper factory
* Useful when mapper needs lazy initialization or configuration
* @param testInstance the test instance containing JacksonTester fields
* @param jsonMapperFactory factory to create the JSON mapper
*/
public static void initFields(Object testInstance, ObjectFactory<JsonMapper> jsonMapperFactory);
// ===== JSON View Support =====
/**
* Create a new tester instance with a specific JSON view for filtering
* Useful for testing different view-based serializations (public vs internal)
* @param view the JSON view class
* @return new JacksonTester instance configured with the view
*/
public JacksonTester<T> forView(Class<?> view);
// ===== Writing (Serialization) =====
/**
* Write an object to JSON and return JsonContent for assertions
* Primary method for testing serialization
* @param value the value to write (must not be null)
* @return JsonContent containing the serialized JSON with assertion support
* @throws IOException on serialization error
*/
public JsonContent<T> write(T value) throws IOException;
// ===== Parsing (from string/bytes) - returns object directly =====
/**
* Parse JSON bytes and return the deserialized object directly
* Use when you only need the object, not assertions on JSON
* @param jsonBytes the source JSON bytes
* @return the deserialized object
* @throws IOException on parse/deserialization error
*/
public T parseObject(byte[] jsonBytes) throws IOException;
/**
* Parse JSON string and return the deserialized object directly
* Use when you only need the object, not assertions on JSON
* @param jsonString the source JSON string
* @return the deserialized object
* @throws IOException on parse/deserialization error
*/
public T parseObject(String jsonString) throws IOException;
// ===== Parsing (from string/bytes) - returns ObjectContent for assertions =====
/**
* Parse JSON bytes into ObjectContent for assertions
* Use parseObject() if you only need the deserialized object without assertions
* @param jsonBytes the source JSON bytes
* @return ObjectContent containing the deserialized object with assertion support
* @throws IOException on parse/deserialization error
*/
public ObjectContent<T> parse(byte[] jsonBytes) throws IOException;
/**
* Parse JSON string into ObjectContent for assertions
* Use parseObject() if you only need the deserialized object without assertions
* @param jsonString the source JSON string
* @return ObjectContent containing the deserialized object with assertion support
* @throws IOException on parse/deserialization error
*/
public ObjectContent<T> parse(String jsonString) throws IOException;
// ===== Reading (from resources/files) - returns object directly =====
/**
* Read object from classpath resource and return directly
* Path may be absolute (/com/example/file.json) or relative to resourceLoadClass
* @param resourcePath the source resource path
* @return the deserialized object
* @throws IOException on read/deserialization error
*/
public T readObject(String resourcePath) throws IOException;
/**
* Read object from file and return directly
* @param file the source file
* @return the deserialized object
* @throws IOException on read/deserialization error
*/
public T readObject(File file) throws IOException;
/**
* Read object from input stream and return directly
* @param inputStream the source input stream
* @return the deserialized object
* @throws IOException on read/deserialization error
*/
public T readObject(InputStream inputStream) throws IOException;
/**
* Read object from Spring Resource and return directly
* @param resource the source resource
* @return the deserialized object
* @throws IOException on read/deserialization error
*/
public T readObject(Resource resource) throws IOException;
/**
* Read object from Reader and return directly
* @param reader the source reader
* @return the deserialized object
* @throws IOException on read/deserialization error
*/
public T readObject(Reader reader) throws IOException;
// ===== Reading (from resources/files) - returns ObjectContent for assertions =====
/**
* Read from classpath resource into ObjectContent for assertions
* Path may be absolute or relative to resourceLoadClass
* @param resourcePath the source resource path
* @return ObjectContent containing the deserialized object with assertion support
* @throws IOException on read/deserialization error
*/
public ObjectContent<T> read(String resourcePath) throws IOException;
/**
* Read from file into ObjectContent for assertions
* @param file the source file
* @return ObjectContent containing the deserialized object with assertion support
* @throws IOException on read/deserialization error
*/
public ObjectContent<T> read(File file) throws IOException;
/**
* Read from input stream into ObjectContent for assertions
* @param inputStream the source input stream
* @return ObjectContent containing the deserialized object with assertion support
* @throws IOException on read/deserialization error
*/
public ObjectContent<T> read(InputStream inputStream) throws IOException;
/**
* Read from Spring Resource into ObjectContent for assertions
* @param resource the source resource
* @return ObjectContent containing the deserialized object with assertion support
* @throws IOException on read/deserialization error
*/
public ObjectContent<T> read(Resource resource) throws IOException;
/**
* Read from Reader into ObjectContent for assertions
* @param reader the source reader
* @return ObjectContent containing the deserialized object with assertion support
* @throws IOException on read/deserialization error
*/
public ObjectContent<T> read(Reader reader) throws IOException;
}import org.springframework.boot.test.json.JacksonTester;
import tools.jackson.databind.json.JsonMapper;
import org.junit.jupiter.api.BeforeEach;
class UserJsonTest {
private JacksonTester<User> json;
@BeforeEach
void setup() {
// Initializes ALL JacksonTester fields in this test class
JsonMapper mapper = JsonMapper.builder().build();
JacksonTester.initFields(this, mapper);
}
}import org.springframework.boot.test.json.JacksonTester;
import tools.jackson.databind.json.JsonMapper;
import tools.jackson.databind.SerializationFeature;
import tools.jackson.datatype.jsr310.JavaTimeModule;
import org.junit.jupiter.api.BeforeEach;
class MultipleJsonTestersTest {
// All JacksonTester fields are automatically initialized
private JacksonTester<User> userJson;
private JacksonTester<Product> productJson;
private JacksonTester<Order> orderJson;
private JacksonTester<List<User>> userListJson;
@BeforeEach
void setup() {
// Configure mapper for all testers
JsonMapper mapper = JsonMapper.builder()
.addModule(new JavaTimeModule())
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
.configure(SerializationFeature.INDENT_OUTPUT, true)
.build();
// Initializes all JacksonTester fields via reflection
JacksonTester.initFields(this, mapper);
}
@Test
void testAllTesters() throws Exception {
// All testers are now ready to use with shared configuration
assertThat(userJson.write(new User("test"))).isNotNull();
assertThat(productJson.write(new Product("item"))).isNotNull();
assertThat(orderJson.write(new Order(1))).isNotNull();
List<User> users = Arrays.asList(new User("user1"), new User("user2"));
assertThat(userListJson.write(users)).isNotNull();
}
}import org.springframework.boot.test.json.JacksonTester;
import org.springframework.core.ResolvableType;
import tools.jackson.databind.json.JsonMapper;
class GenericTypeJsonTest {
private JacksonTester<User> userJson;
private JacksonTester<List<User>> userListJson;
private JacksonTester<Map<String, Product>> productMapJson;
@BeforeEach
void setup() {
JsonMapper mapper = JsonMapper.builder().build();
// Simple type - can use initFields
userJson = new JacksonTester<>(
getClass(),
ResolvableType.forClass(User.class),
mapper
);
// Generic List<User> type
userListJson = new JacksonTester<>(
getClass(),
ResolvableType.forClassWithGenerics(List.class, User.class),
mapper
);
// Generic Map<String, Product> type
productMapJson = new JacksonTester<>(
getClass(),
ResolvableType.forClassWithGenerics(Map.class, String.class, Product.class),
mapper
);
}
}import org.springframework.beans.factory.ObjectFactory;
import tools.jackson.databind.json.JsonMapper;
class FactoryBasedSetupTest {
private JacksonTester<User> json;
@BeforeEach
void setup() {
// Useful when mapper needs lazy initialization
ObjectFactory<JsonMapper> mapperFactory = () -> {
return JsonMapper.builder()
.addModule(new JavaTimeModule())
.build();
};
JacksonTester.initFields(this, mapperFactory);
}
}@Test
void testSerialization() throws Exception {
User user = new User("john", "john@example.com");
JsonContent<User> result = json.write(user);
// Equality assertions
assertThat(result).isEqualToJson("expected-user.json");
assertThat(result).isEqualToJson("{\"name\":\"john\"}");
// JSON path existence
assertThat(result).hasJsonPathValue("$.name");
// JSON path type assertions
assertThat(result).hasJsonPathStringValue("$.name");
// Extract and assert
assertThat(result)
.extractingJsonPathStringValue("$.email")
.isEqualTo("john@example.com");
}@Test
void testDeserialization() throws Exception {
String jsonString = "{\"name\":\"john\",\"email\":\"john@example.com\"}";
// Option 1: Direct object (for simple assertions)
User user = json.parseObject(jsonString);
assertThat(user.getName()).isEqualTo("john");
// Option 2: ObjectContent (for complex assertions)
ObjectContent<User> content = json.parse(jsonString);
assertThat(content.getObject())
.extracting("name", "email")
.containsExactly("john", "john@example.com");
}Full Package: org.springframework.boot.test.json.JsonContent<T>
Wrapper for JSON content with AssertJ assertion support.
// Equality (lenient - ignores array order, extensible objects)
isEqualToJson(CharSequence expected)
isEqualToJson(String path, Class<?> resourceLoadClass)
isEqualToJson(File expected)
isNotEqualToJson(CharSequence expected)
// Equality (strict - enforces order, exact structure)
isStrictlyEqualToJson(CharSequence expected)
isNotStrictlyEqualToJson(CharSequence expected)
// JSON path existence
hasJsonPath(CharSequence expression, Object... args) // Path exists (may be null)
hasJsonPathValue(CharSequence expression, Object... args) // Path exists with non-null value
doesNotHaveJsonPath(CharSequence expression, Object... args)
doesNotHaveJsonPathValue(CharSequence expression, Object... args)
// JSON path type checks
hasJsonPathStringValue(CharSequence expression, Object... args)
hasJsonPathNumberValue(CharSequence expression, Object... args)
hasJsonPathBooleanValue(CharSequence expression, Object... args)
hasJsonPathArrayValue(CharSequence expression, Object... args)
hasJsonPathMapValue(CharSequence expression, Object... args)
hasEmptyJsonPathValue(CharSequence expression, Object... args)
// JSON path value extraction (returns typed AssertJ assertions)
extractingJsonPathValue(CharSequence expression, Object... args) // Returns ObjectAssert
extractingJsonPathStringValue(CharSequence expression, Object... args) // Returns StringAssert
extractingJsonPathNumberValue(CharSequence expression, Object... args) // Returns NumberAssert
extractingJsonPathBooleanValue(CharSequence expression, Object... args) // Returns BooleanAssert
extractingJsonPathArrayValue(CharSequence expression, Object... args) // Returns ListAssert
extractingJsonPathMapValue(CharSequence expression, Object... args) // Returns MapAssert@Test
void testJsonPathAssertions() throws Exception {
Order order = new Order(123, "john", List.of("item1", "item2"));
JsonContent<Order> result = json.write(order);
// Existence
assertThat(result).hasJsonPathValue("$.orderId");
// Type-specific
assertThat(result).hasJsonPathNumberValue("$.orderId");
assertThat(result).hasJsonPathStringValue("$.customer");
assertThat(result).hasJsonPathArrayValue("$.items");
// Extraction and assertion
assertThat(result)
.extractingJsonPathArrayValue("$.items")
.hasSize(2)
.contains("item1", "item2");
// Array element access
assertThat(result)
.extractingJsonPathStringValue("$.items[0]")
.isEqualTo("item1");
}Full Package: org.springframework.boot.test.json.BasicJsonTester
Tests raw JSON strings without object marshalling.
public class BasicJsonTester {
protected BasicJsonTester();
public BasicJsonTester(Class<?> resourceLoadClass);
public BasicJsonTester(Class<?> resourceLoadClass, Charset charset); // @since 1.4.1
public JsonContent<Object> from(CharSequence source); // String or .json resource
public JsonContent<Object> from(String path, Class<?> resourceLoadClass);
public JsonContent<Object> from(byte[] source);
public JsonContent<Object> from(File source);
public JsonContent<Object> from(InputStream source);
public JsonContent<Object> from(Resource source);
// Protected initialization methods
protected final void initialize(Class<?> resourceLoadClass);
protected final void initialize(Class<?> resourceLoadClass, Charset charset); // @since 1.4.1
}BasicJsonTester json = new BasicJsonTester(getClass());
@Test
void testRawJson() {
JsonContent<Object> content = json.from("{\"status\":\"ok\"}");
assertThat(content).hasJsonPathStringValue("$.status");
}Full Package: org.springframework.boot.test.json.GsonTester<T>
public class GsonTester<T> extends AbstractJsonMarshalTester<T> {
protected GsonTester(Gson gson);
public GsonTester(Class<?> resourceLoadClass, ResolvableType type, Gson gson);
public static void initFields(Object testInstance, Gson gson);
public static void initFields(Object testInstance, ObjectFactory<Gson> gsonFactory);
}import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
private GsonTester<User> json;
@BeforeEach
void setup() {
Gson gson = new GsonBuilder().setPrettyPrinting().create();
GsonTester.initFields(this, gson);
}Full Package: org.springframework.boot.test.json.JsonbTester<T>
public class JsonbTester<T> extends AbstractJsonMarshalTester<T> {
protected JsonbTester(Jsonb jsonb);
public JsonbTester(Class<?> resourceLoadClass, ResolvableType type, Jsonb jsonb);
public static void initFields(Object testInstance, Jsonb jsonb);
public static void initFields(Object testInstance, ObjectFactory<Jsonb> jsonbFactory);
}import jakarta.json.bind.Jsonb;
import jakarta.json.bind.JsonbBuilder;
private JsonbTester<User> json;
@BeforeEach
void setup() {
Jsonb jsonb = JsonbBuilder.create();
JsonbTester.initFields(this, jsonb);
}Full Package: org.springframework.boot.test.json.AbstractJsonMarshalTester<T>
Since: 1.4.0
Base class for all JSON marshal testers. Provides common serialization/deserialization operations.
public abstract class AbstractJsonMarshalTester<T> {
protected AbstractJsonMarshalTester(Class<?> resourceLoadClass, ResolvableType type);
// ===== Writing (Serialization) =====
public JsonContent<T> write(T value) throws IOException;
// ===== Parsing (from string/bytes) - returns object directly =====
public T parseObject(byte[] jsonBytes) throws IOException;
public T parseObject(String jsonString) throws IOException;
// ===== Parsing (from string/bytes) - returns ObjectContent for assertions =====
public ObjectContent<T> parse(byte[] jsonBytes) throws IOException;
public ObjectContent<T> parse(String jsonString) throws IOException;
// ===== Reading (from resources) - returns object directly =====
public T readObject(String resourcePath) throws IOException;
public T readObject(File file) throws IOException;
public T readObject(InputStream inputStream) throws IOException;
public T readObject(Resource resource) throws IOException;
public T readObject(Reader reader) throws IOException;
// ===== Reading (from resources) - returns ObjectContent for assertions =====
public ObjectContent<T> read(String resourcePath) throws IOException;
public ObjectContent<T> read(File file) throws IOException;
public ObjectContent<T> read(InputStream inputStream) throws IOException;
public ObjectContent<T> read(Resource resource) throws IOException;
public ObjectContent<T> read(Reader reader) throws IOException;
}Key Distinction:
parseObject()/readObject() → Returns T directly (for simple assertions on object)parse()/read() → Returns ObjectContent<T> (for AssertJ assertions)Full Package: org.springframework.boot.test.json.Jackson2Tester<T>
Since: 4.0.0
Deprecated: For removal in 4.2.0 (use JacksonTester with Jackson 3 instead)
Jackson 2 (com.fasterxml.jackson) JSON tester for backward compatibility.
@Deprecated(since = "4.0.0", forRemoval = true)
public class Jackson2Tester<T> extends AbstractJsonMarshalTester<T> {
// Constructors
protected Jackson2Tester(ObjectMapper objectMapper);
public Jackson2Tester(Class<?> resourceLoadClass, ResolvableType type, ObjectMapper objectMapper);
public Jackson2Tester(Class<?> resourceLoadClass, ResolvableType type, ObjectMapper objectMapper, Class<?> view);
// Initialization
public static void initFields(Object testInstance, ObjectMapper objectMapper);
public static void initFields(Object testInstance, ObjectFactory<ObjectMapper> factory);
// View support
public Jackson2Tester<T> forView(Class<?> view);
}Migration Path:
// Old (Jackson 2)
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.test.json.Jackson2Tester;
private Jackson2Tester<User> json;
@BeforeEach
void setup() {
ObjectMapper mapper = new ObjectMapper();
Jackson2Tester.initFields(this, mapper);
}
// New (Jackson 3)
import tools.jackson.databind.json.JsonMapper;
import org.springframework.boot.test.json.JacksonTester;
private JacksonTester<User> json;
@BeforeEach
void setup() {
JsonMapper mapper = JsonMapper.builder().build();
JacksonTester.initFields(this, mapper);
}public class JacksonTester<T> extends AbstractJsonMarshalTester<T> {
// ===== Constructors =====
protected JacksonTester(JsonMapper jsonMapper); // For subclassing
public JacksonTester(Class<?> resourceLoadClass, ResolvableType type, JsonMapper jsonMapper);
public JacksonTester(Class<?> resourceLoadClass, ResolvableType type, JsonMapper jsonMapper, Class<?> view);
// ===== Static Initialization =====
public static void initFields(Object testInstance, JsonMapper jsonMapper);
public static void initFields(Object testInstance, ObjectFactory<JsonMapper> jsonMapperFactory);
// ===== JSON View Support =====
public JacksonTester<T> forView(Class<?> view);
// Inherits all write/parse/read methods from AbstractJsonMarshalTester
}public class GsonTester<T> extends AbstractJsonMarshalTester<T> {
// ===== Constructors =====
protected GsonTester(Gson gson); // For subclassing
public GsonTester(Class<?> resourceLoadClass, ResolvableType type, Gson gson);
// ===== Static Initialization =====
public static void initFields(Object testInstance, Gson gson);
public static void initFields(Object testInstance, ObjectFactory<Gson> gsonFactory);
// Inherits all write/parse/read methods from AbstractJsonMarshalTester
}public class JsonbTester<T> extends AbstractJsonMarshalTester<T> {
// ===== Constructors =====
protected JsonbTester(Jsonb jsonb); // For subclassing
public JsonbTester(Class<?> resourceLoadClass, ResolvableType type, Jsonb jsonb);
// ===== Static Initialization =====
public static void initFields(Object testInstance, Jsonb jsonb);
public static void initFields(Object testInstance, ObjectFactory<Jsonb> jsonbFactory);
// Inherits all write/parse/read methods from AbstractJsonMarshalTester
}public class BasicJsonTester {
// ===== Constructors =====
public BasicJsonTester(Class<?> resourceLoadClass);
public BasicJsonTester(Class<?> resourceLoadClass, Charset charset);
// ===== Creating JsonContent from Various Sources =====
public JsonContent<Object> from(CharSequence source); // String or .json resource name
public JsonContent<Object> from(String path, Class<?> resourceLoadClass);
public JsonContent<Object> from(byte[] source);
public JsonContent<Object> from(File source);
public JsonContent<Object> from(InputStream source);
public JsonContent<Object> from(Resource source);
}Full Package: org.springframework.boot.test.json.JsonContent<T>
Wrapper for JSON content with AssertJ assertion support.
public final class JsonContent<T> implements AssertProvider<JsonContentAssert> {
public JsonContent(Class<?> resourceLoadClass, ResolvableType type, String json);
// Get raw JSON string
public String getJson();
// Get AssertJ assertions (deprecated - use assertThat(jsonContent) instead)
@Deprecated(since = "1.5.7")
public JsonContentAssert assertThat();
}Full Package: org.springframework.boot.test.json.JsonContentAssert
Comprehensive AssertJ assertions for JSON content validation.
public class JsonContentAssert extends AbstractAssert<JsonContentAssert, CharSequence> {
// ===== Constructors =====
public JsonContentAssert(Class<?> resourceLoadClass, CharSequence json);
public JsonContentAssert(Class<?> resourceLoadClass, Charset charset, CharSequence json);
// ===== Lenient Equality Assertions =====
// Ignores array order and allows extensible objects
public JsonContentAssert isEqualToJson(CharSequence expected);
public JsonContentAssert isEqualToJson(String path, Class<?> resourceLoadClass);
public JsonContentAssert isEqualToJson(byte[] expected);
public JsonContentAssert isEqualToJson(File expected);
public JsonContentAssert isEqualToJson(InputStream expected);
public JsonContentAssert isEqualToJson(Resource expected);
// ===== Strict Equality Assertions =====
// Enforces array order and exact object structure
public JsonContentAssert isStrictlyEqualToJson(CharSequence expected);
public JsonContentAssert isStrictlyEqualToJson(String path, Class<?> resourceLoadClass);
public JsonContentAssert isStrictlyEqualToJson(byte[] expected);
public JsonContentAssert isStrictlyEqualToJson(File expected);
public JsonContentAssert isStrictlyEqualToJson(InputStream expected);
public JsonContentAssert isStrictlyEqualToJson(Resource expected);
// ===== Custom Comparison =====
public JsonContentAssert isEqualToJson(CharSequence expected, JSONCompareMode compareMode);
public JsonContentAssert isEqualToJson(CharSequence expected, JSONComparator comparator);
// ===== Inequality Assertions =====
public JsonContentAssert isNotEqualToJson(CharSequence expected);
public JsonContentAssert isNotStrictlyEqualToJson(CharSequence expected);
public JsonContentAssert isNotEqualToJson(CharSequence expected, JSONCompareMode compareMode);
// ===== JSON Path Existence =====
public JsonContentAssert hasJsonPath(CharSequence expression, Object... args);
public JsonContentAssert hasJsonPathValue(CharSequence expression, Object... args);
public JsonContentAssert doesNotHaveJsonPath(CharSequence expression, Object... args);
public JsonContentAssert doesNotHaveJsonPathValue(CharSequence expression, Object... args);
// ===== JSON Path Type Checks =====
public JsonContentAssert hasJsonPathStringValue(CharSequence expression, Object... args);
public JsonContentAssert hasJsonPathNumberValue(CharSequence expression, Object... args);
public JsonContentAssert hasJsonPathBooleanValue(CharSequence expression, Object... args);
public JsonContentAssert hasJsonPathArrayValue(CharSequence expression, Object... args);
public JsonContentAssert hasJsonPathMapValue(CharSequence expression, Object... args);
public JsonContentAssert hasEmptyJsonPathValue(CharSequence expression, Object... args);
public JsonContentAssert doesNotHaveEmptyJsonPathValue(CharSequence expression, Object... args);
// ===== JSON Path Value Extraction =====
// Returns typed AssertJ assertions for further chaining
public AbstractObjectAssert<?, Object> extractingJsonPathValue(CharSequence expression, Object... args);
public AbstractCharSequenceAssert<?, String> extractingJsonPathStringValue(CharSequence expression, Object... args);
public AbstractObjectAssert<?, Number> extractingJsonPathNumberValue(CharSequence expression, Object... args);
public AbstractBooleanAssert<?> extractingJsonPathBooleanValue(CharSequence expression, Object... args);
public <E> ListAssert<E> extractingJsonPathArrayValue(CharSequence expression, Object... args);
public <K, V> MapAssert<K, V> extractingJsonPathMapValue(CharSequence expression, Object... args);
}Full Package: org.springframework.boot.test.json.ObjectContent<T>
Wrapper for deserialized object content with assertion support.
public final class ObjectContent<T> implements AssertProvider<ObjectContentAssert<T>> {
public ObjectContent(ResolvableType type, T object);
// Get deserialized object
public T getObject();
// Get AssertJ assertions
public ObjectContentAssert<T> assertThat();
}Full Package: org.springframework.boot.test.json.ObjectContentAssert<A>
AssertJ assertions for deserialized objects.
public class ObjectContentAssert<A> extends AbstractObjectAssert<ObjectContentAssert<A>, A> {
// Type conversion assertions
public AbstractObjectArrayAssert<?, Object> asArray();
public AbstractMapAssert<?, ?, Object, Object> asMap();
// Inherits all standard object assertions from AbstractObjectAssert
}Usage:
ObjectContent<User> content = json.parse("{\"name\":\"john\"}");
assertThat(content.getObject().getName()).isEqualTo("john");
// Or use assertions
content.assertThat()
.extracting("name", "email")
.containsExactly("john", "john@example.com");When you need fine-grained control over tester initialization:
import org.springframework.core.ResolvableType;
import tools.jackson.databind.json.JsonMapper;
class ManualInitTest {
private JacksonTester<User> userJson;
private JacksonTester<List<User>> userListJson;
@BeforeEach
void setup() {
JsonMapper mapper = JsonMapper.builder().build();
// Simple type
userJson = new JacksonTester<>(
getClass(),
ResolvableType.forClass(User.class),
mapper
);
// Generic type
userListJson = new JacksonTester<>(
getClass(),
ResolvableType.forClassWithGenerics(List.class, User.class),
mapper
);
}
}@Test
void allReadingOptions() throws Exception {
// From classpath resource (relative to test class)
User u1 = json.readObject("user.json");
// From absolute classpath
User u2 = json.readObject("/com/example/user.json");
// From File
User u3 = json.readObject(new File("test-data/user.json"));
// From InputStream
InputStream stream = getClass().getResourceAsStream("user.json");
User u4 = json.readObject(stream);
// From Spring Resource
Resource resource = new ClassPathResource("user.json");
User u5 = json.readObject(resource);
// From Reader
Reader reader = new StringReader("{\"name\":\"john\"}");
User u6 = json.readObject(reader);
}@Test
void allComparisonOptions() throws Exception {
JsonContent<User> result = json.write(new User("john"));
// String comparison
assertThat(result).isEqualToJson("{\"name\":\"john\"}");
// Resource comparison
assertThat(result).isEqualToJson("expected.json");
// File comparison
assertThat(result).isEqualToJson(new File("expected.json"));
// Byte array comparison
assertThat(result).isEqualToJson("{\"name\":\"john\"}".getBytes());
// InputStream comparison
InputStream stream = new FileInputStream("expected.json");
assertThat(result).isEqualToJson(stream);
// Spring Resource comparison
Resource resource = new ClassPathResource("expected.json");
assertThat(result).isEqualToJson(resource);
// Cross-class resource loading
assertThat(result).isEqualToJson("expected.json", OtherClass.class);
}import org.skyscreamer.jsonassert.JSONCompareMode;
import org.skyscreamer.jsonassert.comparator.CustomComparator;
@Test
void comparisonModes() throws Exception {
JsonContent<Order> result = json.write(order);
// LENIENT: Ignores order, allows extra fields (default)
assertThat(result).isEqualToJson(expected, JSONCompareMode.LENIENT);
// STRICT: Enforces order, no extra fields
assertThat(result).isEqualToJson(expected, JSONCompareMode.STRICT);
// STRICT_ORDER: Enforces order, allows extra fields
assertThat(result).isEqualToJson(expected, JSONCompareMode.STRICT_ORDER);
// NON_EXTENSIBLE: No extra fields, ignores order
assertThat(result).isEqualToJson(expected, JSONCompareMode.NON_EXTENSIBLE);
// Custom comparator
CustomComparator comparator = new CustomComparator(
JSONCompareMode.LENIENT,
// Custom comparison for specific paths
new Customization("order.timestamp", (o1, o2) -> true)
);
assertThat(result).isEqualToJson(expected, comparator);
}import org.springframework.boot.test.json.JacksonTester;
import static org.assertj.core.api.Assertions.assertThat;
class NullHandlingTest {
private JacksonTester<User> json;
@Test
void testNullFields() throws Exception {
User user = new User("john", null, null); // email and age are null
JsonContent<User> result = json.write(user);
// Verify null fields based on mapper configuration
assertThat(result).hasJsonPathValue("$.name");
// Behavior depends on serializationInclusion configuration:
// - JsonInclude.Include.NON_NULL: doesn't have email/age paths
// - JsonInclude.Include.ALWAYS: has paths with null values
}
@Test
void testEmptyCollections() throws Exception {
Order order = new Order(123).withItems(Collections.emptyList());
JsonContent<Order> result = json.write(order);
assertThat(result).hasJsonPathValue("$.items");
assertThat(result).hasEmptyJsonPathValue("$.items");
assertThat(result)
.extractingJsonPathArrayValue("$.items")
.isEmpty();
}
@Test
void testNullCollectionFields() throws Exception {
Order order = new Order(123).withItems(null);
JsonContent<Order> result = json.write(order);
// With NON_NULL inclusion
assertThat(result).doesNotHaveJsonPath("$.items");
// With ALWAYS inclusion
// assertThat(result).hasJsonPath("$.items");
// assertThat(result).doesNotHaveJsonPathValue("$.items");
}
}import tools.jackson.databind.json.JsonMapper;
import tools.jackson.databind.module.SimpleModule;
import tools.jackson.core.JsonGenerator;
import tools.jackson.core.JsonParser;
import tools.jackson.databind.JsonSerializer;
import tools.jackson.databind.JsonDeserializer;
import tools.jackson.databind.SerializerProvider;
import tools.jackson.databind.DeserializationContext;
class CustomSerializationTest {
private JacksonTester<Money> json;
// Custom serializer for Money type
static class MoneySerializer extends JsonSerializer<Money> {
@Override
public void serialize(Money value, JsonGenerator gen, SerializerProvider provider)
throws IOException {
gen.writeStartObject();
gen.writeNumberField("cents", value.getCents());
gen.writeStringField("currency", value.getCurrency());
gen.writeStringField("formatted", value.format());
gen.writeEndObject();
}
}
// Custom deserializer for Money type
static class MoneyDeserializer extends JsonDeserializer<Money> {
@Override
public Money deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException {
JsonNode node = p.getCodec().readTree(p);
int cents = node.get("cents").asInt();
String currency = node.get("currency").asText();
return new Money(cents, currency);
}
}
@BeforeEach
void setup() {
SimpleModule module = new SimpleModule();
module.addSerializer(Money.class, new MoneySerializer());
module.addDeserializer(Money.class, new MoneyDeserializer());
JsonMapper mapper = JsonMapper.builder()
.addModule(module)
.build();
JacksonTester.initFields(this, mapper);
}
@Test
void testCustomSerialization() throws Exception {
Money money = new Money(1250, "USD"); // $12.50
JsonContent<Money> result = json.write(money);
assertThat(result).hasJsonPathNumberValue("$.cents");
assertThat(result).hasJsonPathStringValue("$.currency");
assertThat(result).hasJsonPathStringValue("$.formatted");
assertThat(result)
.extractingJsonPathStringValue("$.formatted")
.isEqualTo("$12.50");
}
@Test
void testCustomDeserialization() throws Exception {
String jsonString = """
{
"cents": 2500,
"currency": "USD",
"formatted": "$25.00"
}
""";
Money money = json.parseObject(jsonString);
assertThat(money.getCents()).isEqualTo(2500);
assertThat(money.getCurrency()).isEqualTo("USD");
}
}import tools.jackson.annotation.JsonTypeInfo;
import tools.jackson.annotation.JsonSubTypes;
import tools.jackson.annotation.JsonTypeName;
class PolymorphicTypeTest {
private JacksonTester<Animal> json;
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
@JsonSubTypes({
@JsonSubTypes.Type(value = Dog.class, name = "dog"),
@JsonSubTypes.Type(value = Cat.class, name = "cat")
})
interface Animal {
String getName();
}
@JsonTypeName("dog")
static class Dog implements Animal {
private String name;
private String breed;
// getters/setters
}
@JsonTypeName("cat")
static class Cat implements Animal {
private String name;
private int lives;
// getters/setters
}
@Test
void testPolymorphicSerialization() throws Exception {
Animal dog = new Dog("Rex", "Labrador");
JsonContent<Animal> result = json.write(dog);
// Type information is included
assertThat(result).hasJsonPathStringValue("$.type");
assertThat(result)
.extractingJsonPathStringValue("$.type")
.isEqualTo("dog");
assertThat(result).hasJsonPathStringValue("$.breed");
}
@Test
void testPolymorphicDeserialization() throws Exception {
String jsonString = """
{
"type": "cat",
"name": "Whiskers",
"lives": 9
}
""";
Animal animal = json.parseObject(jsonString);
assertThat(animal).isInstanceOf(Cat.class);
Cat cat = (Cat) animal;
assertThat(cat.getName()).isEqualTo("Whiskers");
assertThat(cat.getLives()).isEqualTo(9);
}
}import tools.jackson.annotation.JsonIgnore;
import tools.jackson.annotation.JsonIgnoreProperties;
import tools.jackson.annotation.JsonProperty;
class IgnorePropertiesTest {
private JacksonTester<SecureUser> json;
@JsonIgnoreProperties({"internalId", "metadata"})
static class SecureUser {
private String username;
@JsonIgnore
private String password;
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private String ssn;
private String email;
private Long internalId;
// getters/setters
}
@Test
void testIgnoredFieldsNotSerialized() throws Exception {
SecureUser user = new SecureUser();
user.setUsername("john");
user.setPassword("secret123");
user.setSsn("123-45-6789");
user.setEmail("john@example.com");
user.setInternalId(12345L);
JsonContent<SecureUser> result = json.write(user);
// Normal fields present
assertThat(result).hasJsonPathStringValue("$.username");
assertThat(result).hasJsonPathStringValue("$.email");
// Ignored fields not present
assertThat(result).doesNotHaveJsonPath("$.password");
assertThat(result).doesNotHaveJsonPath("$.ssn");
assertThat(result).doesNotHaveJsonPath("$.internalId");
assertThat(result).doesNotHaveJsonPath("$.metadata");
}
@Test
void testWriteOnlyFieldsCanDeserialize() throws Exception {
// ssn has WRITE_ONLY access - can deserialize but not serialize
String jsonString = """
{
"username": "john",
"password": "ignored",
"ssn": "987-65-4321",
"email": "john@example.com"
}
""";
SecureUser user = json.parseObject(jsonString);
assertThat(user.getUsername()).isEqualTo("john");
assertThat(user.getSsn()).isEqualTo("987-65-4321"); // Can deserialize
assertThat(user.getPassword()).isNull(); // @JsonIgnore prevents deserialization
}
}class NestedGenericsTest {
private JacksonTester<Map<String, List<Product>>> json;
@BeforeEach
void setup() {
JsonMapper mapper = JsonMapper.builder().build();
// Nested generic: Map<String, List<Product>>
json = new JacksonTester<>(
getClass(),
ResolvableType.forClassWithGenerics(
Map.class,
String.class,
ResolvableType.forClassWithGenerics(List.class, Product.class)
),
mapper
);
}
@Test
void testNestedGenericSerialization() throws Exception {
Map<String, List<Product>> catalog = Map.of(
"electronics", List.of(
new Product("laptop", 999.99),
new Product("phone", 499.99)
),
"books", List.of(
new Product("novel", 12.99),
new Product("textbook", 89.99)
)
);
JsonContent<Map<String, List<Product>>> result = json.write(catalog);
assertThat(result).hasJsonPathArrayValue("$.electronics");
assertThat(result).hasJsonPathArrayValue("$.books");
assertThat(result)
.extractingJsonPathArrayValue("$.electronics")
.hasSize(2);
assertThat(result)
.extractingJsonPathStringValue("$.electronics[0].name")
.isEqualTo("laptop");
}
}import tools.jackson.databind.json.JsonMapper;
import tools.jackson.datatype.jsr310.JavaTimeModule;
import java.time.*;
import java.time.format.DateTimeFormatter;
class DateTimeEdgeCasesTest {
private JacksonTester<Event> json;
@BeforeEach
void setup() {
JsonMapper mapper = JsonMapper.builder()
.addModule(new JavaTimeModule())
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
.build();
JacksonTester.initFields(this, mapper);
}
@Test
void testTimezoneHandling() throws Exception {
ZonedDateTime utcTime = ZonedDateTime.of(
2024, 3, 15, 10, 0, 0, 0,
ZoneId.of("UTC")
);
Event event = new Event("Meeting", utcTime);
JsonContent<Event> result = json.write(event);
// ISO-8601 format with timezone
assertThat(result)
.extractingJsonPathStringValue("$.timestamp")
.contains("Z"); // UTC indicator
}
@Test
void testNullDateHandling() throws Exception {
Event event = new Event("TBD", null);
JsonContent<Event> result = json.write(event);
assertThat(result).hasJsonPathStringValue("$.name");
// Depending on configuration:
// - NON_NULL: doesn't have timestamp path
// - ALWAYS: has timestamp with null value
}
@Test
void testInstantVsLocalDateTimeSerialization() throws Exception {
Instant instant = Instant.parse("2024-03-15T10:00:00Z");
LocalDateTime localDateTime = LocalDateTime.of(2024, 3, 15, 10, 0);
// Instant includes timezone information
Event eventWithInstant = new Event("Global", instant);
JsonContent<Event> instantResult = json.write(eventWithInstant);
assertThat(instantResult)
.extractingJsonPathStringValue("$.timestamp")
.contains("Z");
// LocalDateTime does not include timezone
Event eventWithLocal = new Event("Local", localDateTime);
JsonContent<Event> localResult = json.write(eventWithLocal);
assertThat(localResult)
.extractingJsonPathStringValue("$.timestamp")
.doesNotContain("Z");
}
}@Test
void testRoundTrip() throws Exception {
Order original = new Order(123, "john", List.of("item1", "item2"));
// Serialize
JsonContent<Order> jsonContent = json.write(original);
String jsonString = jsonContent.getJson();
// Deserialize
Order deserialized = json.parseObject(jsonString);
// Verify round-trip
assertThat(deserialized)
.usingRecursiveComparison()
.isEqualTo(original);
}import tools.jackson.databind.json.JsonMapper;
import tools.jackson.datatype.jsr310.JavaTimeModule;
import java.time.LocalDateTime;
private JacksonTester<Event> json;
@BeforeEach
void setup() {
JsonMapper mapper = JsonMapper.builder()
.addModule(new JavaTimeModule())
.build();
JacksonTester.initFields(this, mapper);
}
@Test
void testDateTimeSerialization() throws Exception {
Event event = new Event("Meeting", LocalDateTime.of(2024, 3, 15, 10, 0));
assertThat(json.write(event))
.hasJsonPathStringValue("$.name")
.hasJsonPathValue("$.timestamp");
}import tools.jackson.annotation.JsonView;
static class Views {
static class Public {}
static class Internal extends Public {}
}
static class User {
@JsonView(Views.Public.class)
private String name;
@JsonView(Views.Internal.class)
private String ssn;
}
@Test
void testWithViews() throws Exception {
User user = new User("john", "123-45-6789");
// Public view - excludes ssn
JacksonTester<User> publicJson = json.forView(Views.Public.class);
assertThat(publicJson.write(user))
.hasJsonPathValue("$.name")
.doesNotHaveJsonPath("$.ssn");
// Internal view - includes ssn
JacksonTester<User> internalJson = json.forView(Views.Internal.class);
assertThat(internalJson.write(user))
.hasJsonPathValue("$.name")
.hasJsonPathValue("$.ssn");
}@Test
void testNestedObjectStructure() throws Exception {
Order order = new Order(123)
.withCustomer(new Customer("john", "john@example.com"))
.withItems(List.of(
new Item("item1", 10.0),
new Item("item2", 20.0)
))
.withShipping(new Address("123 Main St", "Springfield"));
JsonContent<Order> result = json.write(order);
// Verify top-level
assertThat(result).hasJsonPathNumberValue("$.orderId");
// Verify nested customer
assertThat(result)
.extractingJsonPathStringValue("$.customer.name")
.isEqualTo("john");
// Verify array
assertThat(result)
.extractingJsonPathArrayValue("$.items")
.hasSize(2);
// Verify array element
assertThat(result)
.extractingJsonPathStringValue("$.items[0].name")
.isEqualTo("item1");
// Verify deeply nested
assertThat(result)
.extractingJsonPathStringValue("$.shipping.city")
.isEqualTo("Springfield");
}import org.skyscreamer.jsonassert.JSONCompareMode;
@Test
void testComparisonModes() throws Exception {
Order order = new Order(123, "john");
JsonContent<Order> result = json.write(order);
String expected = """
{
"orderId": 123,
"customer": "john",
"items": []
}
""";
// LENIENT: Ignores order, allows extra fields (default)
assertThat(result).isEqualToJson(expected, JSONCompareMode.LENIENT);
// STRICT: Enforces order, no extra fields
// This would fail if result has extra fields
// assertThat(result).isEqualToJson(expected, JSONCompareMode.STRICT);
// NON_EXTENSIBLE: No extra fields, ignores order
assertThat(result).isEqualToJson(expected, JSONCompareMode.NON_EXTENSIBLE);
}import tools.jackson.databind.SerializationFeature;
import tools.jackson.annotation.JsonInclude;
@BeforeEach
void setupCustomMapper() {
JsonMapper mapper = JsonMapper.builder()
.addModule(new JavaTimeModule())
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
.serializationInclusion(JsonInclude.Include.NON_NULL)
.build();
JacksonTester.initFields(this, mapper);
}
@Test
void testNullFieldsExcluded() throws Exception {
User user = new User("john", null); // email is null
assertThat(json.write(user))
.hasJsonPathValue("$.name")
.doesNotHaveJsonPath("$.email"); // null fields excluded
}@Test
void testErrorResponse() throws Exception {
String errorJson = """
{
"status": 400,
"error": "Bad Request",
"message": "Invalid input",
"path": "/api/users"
}
""";
BasicJsonTester json = new BasicJsonTester(getClass());
JsonContent<Object> content = json.from(errorJson);
assertThat(content)
.extractingJsonPathNumberValue("$.status")
.isEqualTo(400);
assertThat(content)
.extractingJsonPathStringValue("$.message")
.contains("Invalid");
}abstract class JsonTestBase {
protected JacksonTester<User> userJson;
protected JacksonTester<Order> orderJson;
protected JacksonTester<Product> productJson;
@BeforeEach
void setupJson() {
JsonMapper mapper = createConfiguredMapper();
JacksonTester.initFields(this, mapper);
}
protected JsonMapper createConfiguredMapper() {
return JsonMapper.builder()
.addModule(new JavaTimeModule())
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
.serializationInclusion(JsonInclude.Include.NON_NULL)
.build();
}
}
// Concrete test extends base
class UserJsonTest extends JsonTestBase {
@Test
void testUser() throws Exception {
assertThat(userJson.write(new User("test"))).isNotNull();
}
}Spring Boot 4.0 introduces Jackson 3 support while deprecating Jackson 2.
Package Names Changed:
// Old (Jackson 2)
import com.fasterxml.jackson.databind.ObjectMapper;
// New (Jackson 3)
import tools.jackson.databind.json.JsonMapper;Tester Class Changes:
// Old (Jackson 2)
import org.springframework.boot.test.json.Jackson2Tester;
private Jackson2Tester<User> json;
@BeforeEach
void setup() {
ObjectMapper mapper = new ObjectMapper();
Jackson2Tester.initFields(this, mapper);
}
// New (Jackson 3)
import org.springframework.boot.test.json.JacksonTester;
private JacksonTester<User> json;
@BeforeEach
void setup() {
JsonMapper mapper = JsonMapper.builder().build();
JacksonTester.initFields(this, mapper);
}Module Registration:
// Old (Jackson 2)
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
// New (Jackson 3)
JsonMapper mapper = JsonMapper.builder()
.addModule(new JavaTimeModule())
.build();Configuration:
// Old (Jackson 2)
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
// New (Jackson 3)
JsonMapper mapper = JsonMapper.builder()
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
.serializationInclusion(JsonInclude.Include.NON_NULL)
.build();During migration, both can coexist:
// Keep Jackson 2 tests working
import org.springframework.boot.test.json.Jackson2Tester;
import com.fasterxml.jackson.databind.ObjectMapper;
private Jackson2Tester<User> legacyJson;
@BeforeEach
void setupLegacy() {
@SuppressWarnings("deprecation")
ObjectMapper mapper = new ObjectMapper();
Jackson2Tester.initFields(this, mapper);
}
// New tests use Jackson 3
import org.springframework.boot.test.json.JacksonTester;
import tools.jackson.databind.json.JsonMapper;
private JacksonTester<User> json;
@BeforeEach
void setup() {
JsonMapper mapper = JsonMapper.builder().build();
JacksonTester.initFields(this, mapper);
}OutputCaptureRule deprecated - use OutputCaptureExtension:
// Old (JUnit 4)
import org.springframework.boot.test.rule.OutputCaptureRule;
@Rule
public OutputCaptureRule output = new OutputCaptureRule();
// New (JUnit 5)
import org.springframework.boot.test.system.OutputCaptureExtension;
import org.junit.jupiter.api.extension.ExtendWith;
@ExtendWith(OutputCaptureExtension.class)
class MyTest {
@Test
void test(CapturedOutput output) {
// Use output
}
}Issue: NullPointerException on json field
@BeforeEach
void setup() {
JacksonTester.initFields(this, JsonMapper.builder().build());
}Issue: IOException "Cannot deserialize instance"
JsonMapper mapper = JsonMapper.builder()
.enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.build();Issue: Generic type erasure in parseObject()
JacksonTester<List<User>> json = new JacksonTester<>(
getClass(),
ResolvableType.forClassWithGenerics(List.class, User.class),
mapper
);Issue: Date/time fields fail to deserialize
JsonMapper mapper = JsonMapper.builder()
.addModule(new JavaTimeModule())
.build();Issue: JSON path not found
$.name - root level field$.user.email - nested field$.items[0] - first array element$.items[*].name - all item namesIssue: JSON path returns unexpected type
extractingJsonPathStringValue() for stringsextractingJsonPathNumberValue() for numbersextractingJsonPathArrayValue() for arraysextractingJsonPathMapValue() for objectsIssue: Lenient comparison fails unexpectedly
"123" vs 123 are different even in lenient modeIssue: Array order causes comparison failure
Issue: Extra fields cause comparison failure
Issue: Resource file not found
src/test/resources/
com/example/myapp/
expected-user.json
test-data.jsonIssue: Resource loaded with wrong encoding
BasicJsonTester json = new BasicJsonTester(getClass(), StandardCharsets.UTF_8);Issue: Tests run slowly with large JSON
Issue: JSON path evaluation is slow