unit-test-json-serialization

Unit testing JSON serialization and deserialization with Spring's @JsonTest and Jackson. Covers serialization/deserialization of POJOs, custom serializers/deserializers, field name mappings with @JsonProperty , and null handling using JacksonTester for type-safe assertions Includes patterns for testing nested objects, lists, date/time formatting, and polymorphic types with @JsonTypeInfo Provides Maven and Gradle setup, best practices for avoiding circular references and null inclusion issues, and troubleshooting guidance for common Jackson configuration problems Uses @JsonTest annotation for lightweight JSON-focused test context without full Spring application startup

INSTALLATION
npx skills add https://github.com/giuseppe-trisciuoglio/developer-kit --skill unit-test-json-serialization
Run in your project or agent environment. Adjust flags if your CLI version differs.

SKILL.md

Unit Testing JSON Serialization with @JsonTest

Overview

Provides patterns for unit testing JSON serialization and deserialization using Spring's @JsonTest and Jackson. Covers POJO mapping, custom serializers, field name mappings, nested objects, date/time formatting, and polymorphic types.

When to Use

  • Testing JSON serialization/deserialization of DTOs
  • Verifying custom Jackson serializers/deserializers
  • Validating @JsonProperty, @JsonIgnore, and field name mappings
  • Testing date/time format handling (LocalDateTime, Date)
  • Testing null handling and missing fields
  • Testing polymorphic type deserialization

Instructions

  • **Annotate test class with @JsonTest** → Enables JacksonTester auto-configuration
  • Autowire JacksonTester for target type → Provides type-safe JSON assertions
  • Test serialization → Call json.write(object) and assert JSON paths with extractingJsonPath*
  • Test deserialization → Call json.parse(json) or json.parseObject(json) and assert object state
  • Validate round-trip → Serialize, then deserialize, verify same data (if object is properly comparable)
  • Test edge cases → Null values, missing fields, empty collections, invalid JSON
  • Add validation checkpoints: After each assertion, verify the test fails meaningfully with wrong data

Examples

Maven Setup

<dependency>

  <groupId>org.springframework.boot</groupId>

  <artifactId>spring-boot-starter-json</artifactId>

</dependency>

<dependency>

  <groupId>org.springframework.boot</groupId>

  <artifactId>spring-boot-starter-test</artifactId>

  <scope>test</scope>

</dependency>

Gradle Setup

dependencies {

  implementation("org.springframework.boot:spring-boot-starter-json")

  testImplementation("org.springframework.boot:spring-boot-starter-test")

}

Basic Serialization and Deserialization

@JsonTest

class UserDtoJsonTest {

  @Autowired

  private JacksonTester<UserDto> json;

  @Test

  void shouldSerializeUserToJson() throws Exception {

    UserDto user = new UserDto(1L, "Alice", "alice@example.com", 25);

    JsonContent<UserDto> result = json.write(user);

    result

      .extractingJsonPathNumberValue("$.id").isEqualTo(1)

      .extractingJsonPathStringValue("$.name").isEqualTo("Alice")

      .extractingJsonPathStringValue("$.email").isEqualTo("alice@example.com")

      .extractingJsonPathNumberValue("$.age").isEqualTo(25);

  }

  @Test

  void shouldDeserializeJsonToUser() throws Exception {

    String json_content = "{\"id\":1,\"name\":\"Alice\",\"email\":\"alice@example.com\",\"age\":25}";

    UserDto user = json.parse(json_content).getObject();

    assertThat(user.getId()).isEqualTo(1L);

    assertThat(user.getName()).isEqualTo("Alice");

    assertThat(user.getEmail()).isEqualTo("alice@example.com");

    assertThat(user.getAge()).isEqualTo(25);

  }

  @Test

  void shouldHandleNullFields() throws Exception {

    String json_content = "{\"id\":1,\"name\":null,\"email\":\"alice@example.com\"}";

    UserDto user = json.parse(json_content).getObject();

    assertThat(user.getName()).isNull();

  }

}

Custom JSON Properties

public class Order {

  @JsonProperty("order_id")

  private Long id;

  @JsonProperty("total_amount")

  private BigDecimal amount;

  @JsonIgnore

  private String internalNote;

}

@JsonTest

class OrderJsonTest {

  @Autowired

  private JacksonTester<Order> json;

  @Test

  void shouldMapJsonPropertyNames() throws Exception {

    String json_content = "{\"order_id\":123,\"total_amount\":99.99}";

    Order order = json.parse(json_content).getObject();

    assertThat(order.getId()).isEqualTo(123L);

    assertThat(order.getAmount()).isEqualByComparingTo(new BigDecimal("99.99"));

  }

  @Test

  void shouldIgnoreJsonIgnoreFields() throws Exception {

    Order order = new Order(123L, new BigDecimal("99.99"));

    order.setInternalNote("Secret");

    assertThat(json.write(order).json).doesNotContain("internalNote");

  }

}

Nested Objects

public class Product {

  private Long id;

  private String name;

  private Category category;

  private List<Review> reviews;

}

@JsonTest

class ProductJsonTest {

  @Autowired

  private JacksonTester<Product> json;

  @Test

  void shouldSerializeNestedObjects() throws Exception {

    Product product = new Product(1L, "Laptop", new Category(1L, "Electronics"));

    JsonContent<Product> result = json.write(product);

    result

      .extractingJsonPathNumberValue("$.category.id").isEqualTo(1)

      .extractingJsonPathStringValue("$.category.name").isEqualTo("Electronics");

  }

  @Test

  void shouldDeserializeNestedObjects() throws Exception {

    String json_content = "{\"id\":1,\"name\":\"Laptop\",\"category\":{\"id\":1,\"name\":\"Electronics\"}}";

    Product product = json.parse(json_content).getObject();

    assertThat(product.getCategory().getName()).isEqualTo("Electronics");

  }

  @Test

  void shouldHandleListOfNestedObjects() throws Exception {

    String json_content = "{\"id\":1,\"reviews\":[{\"rating\":5},{\"rating\":4}]}";

    Product product = json.parse(json_content).getObject();

    assertThat(product.getReviews()).hasSize(2);

  }

}

Date/Time Formatting

@JsonTest

class DateTimeJsonTest {

  @Autowired

  private JacksonTester<Event> json;

  @Test

  void shouldFormatDateTimeCorrectly() throws Exception {

    LocalDateTime dt = LocalDateTime.of(2024, 1, 15, 10, 30, 0);

    json.write(new Event("Conference", dt))

      .extractingJsonPathStringValue("$.scheduledAt").isEqualTo("2024-01-15T10:30:00");

  }

}

Custom Serializers

public class CustomMoneySerializer extends JsonSerializer<BigDecimal> {

  @Override

  public void serialize(BigDecimal value, JsonGenerator gen, SerializerProvider serializers) throws IOException {

    gen.writeString(value == null ? null : String.format("$%.2f", value));

  }

}

@JsonTest

class CustomSerializerTest {

  @Autowired

  private JacksonTester<Price> json;

  @Test

  void shouldUseCustomSerializer() throws Exception {

    json.write(new Price(new BigDecimal("99.99")))

      .extractingJsonPathStringValue("$.amount").isEqualTo("$99.99");

  }

}

Polymorphic Deserialization

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")

@JsonSubTypes({

  @JsonSubTypes.Type(value = CreditCard.class, name = "credit_card"),

  @JsonSubTypes.Type(value = PayPal.class, name = "paypal")

})

public abstract class PaymentMethod { }

@JsonTest

class PolymorphicJsonTest {

  @Autowired

  private JacksonTester<PaymentMethod> json;

  @Test

  void shouldDeserializeCreditCard() throws Exception {

    String json_content = "{\"type\":\"credit_card\",\"id\":\"card123\"}";

    assertThat(json.parse(json_content).getObject()).isInstanceOf(CreditCard.class);

  }

  @Test

  void shouldDeserializePayPal() throws Exception {

    String json_content = "{\"type\":\"paypal\",\"id\":\"pp123\"}";

    assertThat(json.parse(json_content).getObject()).isInstanceOf(PayPal.class);

  }

}

Best Practices

  • Test serialization AND deserialization for complete coverage
  • Verify JSON paths individually rather than comparing full JSON strings
  • Test null handling explicitly — null fields may be included or excluded depending on @JsonInclude
  • Use extractingJsonPath* methods for precise field assertions
  • Test round-trip: serialize an object, deserialize the JSON, verify the result matches
  • Validate edge cases: empty strings, empty collections, deeply nested structures
  • Group related assertions in a single test for clarity

Constraints and Warnings

  • **@JsonTest loads limited context**: Only JSON-related beans; use @SpringBootTest for full Spring context
  • Jackson version: Ensure annotation versions match the Jackson version in use
  • Date formats: ISO-8601 is default; use @JsonFormat for custom patterns
  • Null handling: Use @JsonInclude(Include.NON_NULL) to exclude nulls from serialization
  • Circular references: Use @JsonManagedReference/@JsonBackReference to prevent infinite loops
  • Immutable objects: Use @JsonCreator + @JsonProperty for constructor-based deserialization
  • Polymorphic types: @JsonTypeInfo must correctly identify the subtype for deserialization to work

Debugging Workflow

When a JSON test fails, follow this workflow:

Failure Symptom

Common Cause

How to Verify

JsonPath assertion fails

Field name mismatch

Check @JsonProperty spelling matches JSON key

Null expected but got value

@JsonInclude(NON_NULL) configured

Verify annotation on field/class

Deserialization returns wrong type

Missing @JsonTypeInfo

Add type info property to JSON or configure subtype mapping

Date format mismatch

Format string incorrect

Confirm @JsonFormat(pattern=...) matches expected string

Missing field in output

@JsonIgnore or transient modifier

Check field for @JsonIgnore or transient keyword

Nested object is null

Inner JSON missing or malformed

Log parsed JSON; verify inner structure matches POJO

JsonParseException

Malformed JSON string

Validate JSON syntax; check for unescaped characters

Validation checkpoint after fixing: Re-run the test — if it passes, write a complementary test for the opposite case (e.g., if you fixed null handling, add a test for non-null values to prevent regression).

References

BrowserAct

Let your agent run on any real-world website

Bypass CAPTCHA & anti-bot for free. Start local, scale to cloud.

Explore BrowserAct Skills →

Stop writing automation&scrapers

Install the CLI. Run your first Skill in 30 seconds. Scale when you're ready.

Start free
free · no credit card