or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

assertj-integration.mdcontext-runners.mdindex.mdintegration-testing.mdjson-testing.mdoutput-capture.mdtest-configuration.mdtest-properties.mdweb-test-utilities.md
tile.json

json-testing.mddocs/

JSON Testing

Comprehensive JSON testing framework with multiple library support (Jackson 3, Jackson 2, Gson, JSON-B) and rich AssertJ-based assertions.

Package

org.springframework.boot.test.json

Prerequisites

  • Jackson 3 (tools.jackson) for JacksonTester OR
  • Jackson 2 (com.fasterxml.jackson) for Jackson2Tester OR
  • Google Gson for GsonTester OR
  • Jakarta JSON-B for JsonbTester

Core Imports

// 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;

JacksonTester (Recommended)

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;
}

Setup Pattern

Pattern: Basic Single Tester Setup

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);
    }
}

Pattern: Multiple Testers with Shared Configuration

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();
    }
}

Pattern: Manual Initialization for Generic Types

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
        );
    }
}

Pattern: Factory-Based Initialization

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);
    }
}

Write and Assert Pattern

@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");
}

Parse and Assert Pattern

@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");
}

JsonContent Assertions

Full Package: org.springframework.boot.test.json.JsonContent<T>

Wrapper for JSON content with AssertJ assertion support.

Key Assertion Methods

// 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

JSON Path Examples

@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");
}

BasicJsonTester

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
}

Pattern: Testing Raw JSON

BasicJsonTester json = new BasicJsonTester(getClass());

@Test
void testRawJson() {
    JsonContent<Object> content = json.from("{\"status\":\"ok\"}");
    assertThat(content).hasJsonPathStringValue("$.status");
}

GsonTester

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);
}

Setup Pattern

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);
}

JsonbTester

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);
}

Setup Pattern

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);
}

Complete API Reference

AbstractJsonMarshalTester (Base Class)

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)

Jackson2Tester (Deprecated)

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);
}

Complete JacksonTester API

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
}

Complete GsonTester API

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
}

Complete JsonbTester API

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
}

Complete BasicJsonTester API

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);
}

JsonContent API

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();
}

Complete JsonContentAssert API

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);
}

ObjectContent API

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();
}

ObjectContentAssert API

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");

Advanced Usage Patterns

Manual Tester Construction

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
        );
    }
}

All Resource Loading Options

@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);
}

All Comparison Options

@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);
}

JSON Comparison Modes

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);
}

Edge Cases and Complex Scenarios

Pattern: Testing Null and Empty Values

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");
    }
}

Pattern: Testing with Custom Serializers/Deserializers

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");
    }
}

Pattern: Testing Polymorphic Types

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);
    }
}

Pattern: Testing with Ignore Properties

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
    }
}

Pattern: Testing Nested Generic Collections

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");
    }
}

Pattern: Testing with Date/Time Edge Cases

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");
    }
}

Additional Usage Patterns

Pattern: Round-Trip Testing

@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);
}

Pattern: Testing Date/Time Serialization

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");
}

Pattern: Testing with JSON Views

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");
}

Pattern: Testing Complex Nested Objects

@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");
}

Pattern: Testing with Multiple JSON Comparison Modes

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);
}

Pattern: Testing with Custom Jackson Configuration

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
}

Pattern: Testing API Response Error Scenarios

@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");
}

Pattern: Reusable Base Test Class

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();
    }
}

Migration Guide

Jackson 2 to Jackson 3 Migration

Spring Boot 4.0 introduces Jackson 3 support while deprecating Jackson 2.

Key Changes

  1. Package Names Changed:

    // Old (Jackson 2)
    import com.fasterxml.jackson.databind.ObjectMapper;
    
    // New (Jackson 3)
    import tools.jackson.databind.json.JsonMapper;
  2. 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);
    }
  3. Module Registration:

    // Old (Jackson 2)
    ObjectMapper mapper = new ObjectMapper();
    mapper.registerModule(new JavaTimeModule());
    
    // New (Jackson 3)
    JsonMapper mapper = JsonMapper.builder()
        .addModule(new JavaTimeModule())
        .build();
  4. 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();

Migration Timeline

  • Spring Boot 4.0: Jackson2Tester deprecated
  • Spring Boot 4.2 (planned): Jackson2Tester removed
  • Recommendation: Migrate to JacksonTester immediately

Coexistence Strategy

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);
}

From JUnit 4 to JUnit 5

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
    }
}

Common Pitfalls and Solutions

Issues with Initialization

Issue: NullPointerException on json field

  • Cause: Forgot to call initFields() in @BeforeEach
  • Solution: Add setup method with initFields()
  • Example:
    @BeforeEach
    void setup() {
        JacksonTester.initFields(this, JsonMapper.builder().build());
    }

Deserialization Issues

Issue: IOException "Cannot deserialize instance"

  • Cause: Type mismatch between JSON and Java class
  • Solution: Verify JSON structure matches class fields
  • Debug: Enable detailed Jackson error messages
    JsonMapper mapper = JsonMapper.builder()
        .enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
        .build();

Issue: Generic type erasure in parseObject()

  • Cause: Cannot deserialize generic types without type info
  • Solution: Use manual construction with ResolvableType.forClassWithGenerics()
  • Example:
    JacksonTester<List<User>> json = new JacksonTester<>(
        getClass(),
        ResolvableType.forClassWithGenerics(List.class, User.class),
        mapper
    );

Issue: Date/time fields fail to deserialize

  • Cause: Missing JavaTimeModule
  • Solution: Add JavaTimeModule to mapper
    JsonMapper mapper = JsonMapper.builder()
        .addModule(new JavaTimeModule())
        .build();

JSON Path Issues

Issue: JSON path not found

  • Cause: Wrong JSON path syntax
  • Solution: Use $ for root, . for nested, [0] for array elements
  • Examples:
    • $.name - root level field
    • $.user.email - nested field
    • $.items[0] - first array element
    • $.items[*].name - all item names

Issue: JSON path returns unexpected type

  • Cause: Using wrong extraction method for type
  • Solution: Match extraction method to JSON type:
    • extractingJsonPathStringValue() for strings
    • extractingJsonPathNumberValue() for numbers
    • extractingJsonPathArrayValue() for arrays
    • extractingJsonPathMapValue() for objects

Comparison Issues

Issue: Lenient comparison fails unexpectedly

  • Cause: Type differences (string vs number)
  • Solution: Ensure types match or use strict comparison
  • Example: "123" vs 123 are different even in lenient mode

Issue: Array order causes comparison failure

  • Cause: Using strict comparison when order doesn't matter
  • Solution: Use lenient comparison or JSONCompareMode.NON_EXTENSIBLE

Issue: Extra fields cause comparison failure

  • Cause: Using strict or non-extensible comparison
  • Solution: Use lenient comparison or filter expected JSON

Resource Loading Issues

Issue: Resource file not found

  • Cause: File not in test resources or wrong path
  • Solution: Place files in src/test/resources matching test package structure
  • Structure:
    src/test/resources/
      com/example/myapp/
        expected-user.json
        test-data.json

Issue: Resource loaded with wrong encoding

  • Cause: Default charset used
  • Solution: Specify charset explicitly
    BasicJsonTester json = new BasicJsonTester(getClass(), StandardCharsets.UTF_8);

Performance Issues

Issue: Tests run slowly with large JSON

  • Cause: Repeated mapper initialization
  • Solution: Cache mapper instance or use base test class

Issue: JSON path evaluation is slow

  • Cause: Complex path expressions on large documents
  • Solution: Simplify paths or parse once and reuse ObjectContent

See Also

  • Integration Testing - Testing REST endpoints
  • Context Runners - Testing JSON in isolation