CtrlK
BlogDocsLog inGet started
Tessl Logo

unit-test-json-serialization

Unit tests for JSON serialization/deserialization with Jackson and @JsonTest. Use when validating JSON mapping, custom serializers, and date format handling.

Install with Tessl CLI

npx tessl i github:giuseppe-trisciuoglio/developer-kit --skill unit-test-json-serialization
What are skills?

81

Does it follow best practices?

Validation for skill structure

SKILL.md
Review
Evals

Unit Testing JSON Serialization with @JsonTest

Test JSON serialization and deserialization of POJOs using Spring's @JsonTest. Verify Jackson configuration, custom serializers, and JSON mapping accuracy.

When to Use This Skill

Use this skill when:

  • Testing JSON serialization of DTOs
  • Testing JSON deserialization to objects
  • Testing custom Jackson serializers/deserializers
  • Verifying JSON field names and formats
  • Testing null handling in JSON
  • Want fast JSON mapping tests without full Spring context

Setup: JSON Testing

Maven

<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>
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
</dependency>

Gradle

dependencies {
  implementation("org.springframework.boot:spring-boot-starter-json")
  implementation("com.fasterxml.jackson.core:jackson-databind")
  testImplementation("org.springframework.boot:spring-boot-starter-test")
}

Basic Pattern: @JsonTest

Test JSON Serialization

import org.springframework.boot.test.autoconfigure.json.JsonTest;
import org.springframework.boot.test.json.JacksonTester;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.*;

@JsonTest
class UserDtoJsonTest {

  @Autowired
  private JacksonTester<UserDto> json;

  @Test
  void shouldSerializeUserToJson() throws Exception {
    UserDto user = new UserDto(1L, "Alice", "alice@example.com", 25);

    org.assertj.core.data.Offset 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)
      .isNotNull()
      .hasFieldOrPropertyWithValue("id", 1L)
      .hasFieldOrPropertyWithValue("name", "Alice")
      .hasFieldOrPropertyWithValue("email", "alice@example.com")
      .hasFieldOrPropertyWithValue("age", 25);
  }

  @Test
  void shouldHandleNullFields() throws Exception {
    String json_content = "{\"id\":1,\"name\":null,\"email\":\"alice@example.com\",\"age\":null}";

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

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

Testing Custom JSON Properties

@JsonProperty and @JsonIgnore

public class Order {
  @JsonProperty("order_id")
  private Long id;

  @JsonProperty("total_amount")
  private BigDecimal amount;

  @JsonIgnore
  private String internalNote;

  private LocalDateTime createdAt;
}

@JsonTest
class OrderJsonTest {

  @Autowired
  private JacksonTester<Order> json;

  @Test
  void shouldMapJsonPropertyNames() throws Exception {
    String json_content = "{\"order_id\":123,\"total_amount\":99.99,\"createdAt\":\"2024-01-15T10:30:00\"}";

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

    assertThat(order.getId()).isEqualTo(123L);
    assertThat(order.getAmount()).isEqualByComparingTo(new BigDecimal("99.99"));
  }

  @Test
  void shouldIgnoreJsonIgnoreAnnotatedFields() throws Exception {
    Order order = new Order(123L, new BigDecimal("99.99"));
    order.setInternalNote("Secret note");

    JsonContent<Order> result = json.write(order);

    assertThat(result.json).doesNotContain("internalNote");
  }
}

Testing List Deserialization

JSON Arrays

@JsonTest
class UserListJsonTest {

  @Autowired
  private JacksonTester<List<UserDto>> json;

  @Test
  void shouldDeserializeUserList() throws Exception {
    String jsonArray = "[{\"id\":1,\"name\":\"Alice\"},{\"id\":2,\"name\":\"Bob\"}]";

    List<UserDto> users = json.parseObject(jsonArray);

    assertThat(users)
      .hasSize(2)
      .extracting(UserDto::getName)
      .containsExactly("Alice", "Bob");
  }

  @Test
  void shouldSerializeUserListToJson() throws Exception {
    List<UserDto> users = List.of(
      new UserDto(1L, "Alice"),
      new UserDto(2L, "Bob")
    );

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

    result.json.contains("Alice").contains("Bob");
  }
}

Testing Nested Objects

Complex JSON Structures

public class Product {
  private Long id;
  private String name;
  private Category category;
  private List<Review> reviews;
}

public class Category {
  private Long id;
  private String name;
}

public class Review {
  private String reviewer;
  private int rating;
  private String comment;
}

@JsonTest
class ProductJsonTest {

  @Autowired
  private JacksonTester<Product> json;

  @Test
  void shouldSerializeNestedObjects() throws Exception {
    Category category = new Category(1L, "Electronics");
    Product product = new Product(1L, "Laptop", category);

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

    result
      .extractingJsonPathNumberValue("$.id").isEqualTo(1)
      .extractingJsonPathStringValue("$.name").isEqualTo("Laptop")
      .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())
      .isNotNull()
      .hasFieldOrPropertyWithValue("name", "Electronics");
  }

  @Test
  void shouldHandleListOfNestedObjects() throws Exception {
    String json_content = "{\"id\":1,\"name\":\"Laptop\",\"reviews\":[{\"reviewer\":\"John\",\"rating\":5},{\"reviewer\":\"Jane\",\"rating\":4}]}";

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

    assertThat(product.getReviews())
      .hasSize(2)
      .extracting(Review::getRating)
      .containsExactly(5, 4);
  }
}

Testing Date/Time Formatting

LocalDateTime and Other Temporal Types

@JsonTest
class DateTimeJsonTest {

  @Autowired
  private JacksonTester<Event> json;

  @Test
  void shouldFormatDateTimeCorrectly() throws Exception {
    LocalDateTime dateTime = LocalDateTime.of(2024, 1, 15, 10, 30, 0);
    Event event = new Event("Conference", dateTime);

    JsonContent<Event> result = json.write(event);

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

  @Test
  void shouldDeserializeDateTimeFromJson() throws Exception {
    String json_content = "{\"name\":\"Conference\",\"scheduledAt\":\"2024-01-15T10:30:00\"}";

    Event event = json.parse(json_content).getObject();

    assertThat(event.getScheduledAt())
      .isEqualTo(LocalDateTime.of(2024, 1, 15, 10, 30, 0));
  }
}

Testing Custom Serializers

Custom JsonSerializer Implementation

public class CustomMoneySerializer extends JsonSerializer<BigDecimal> {
  @Override
  public void serialize(BigDecimal value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
    if (value == null) {
      gen.writeNull();
    } else {
      gen.writeString(String.format("$%.2f", value));
    }
  }
}

public class Price {
  @JsonSerialize(using = CustomMoneySerializer.class)
  private BigDecimal amount;
}

@JsonTest
class CustomSerializerTest {

  @Autowired
  private JacksonTester<Price> json;

  @Test
  void shouldUseCustomSerializer() throws Exception {
    Price price = new Price(new BigDecimal("99.99"));

    JsonContent<Price> result = json.write(price);

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

Testing Polymorphic Deserialization

Type Information in JSON

@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 {
  private String id;
}

@JsonTest
class PolymorphicJsonTest {

  @Autowired
  private JacksonTester<PaymentMethod> json;

  @Test
  void shouldDeserializeCreditCard() throws Exception {
    String json_content = "{\"type\":\"credit_card\",\"id\":\"card123\",\"cardNumber\":\"****1234\"}";

    PaymentMethod method = json.parse(json_content).getObject();

    assertThat(method).isInstanceOf(CreditCard.class);
  }

  @Test
  void shouldDeserializePayPal() throws Exception {
    String json_content = "{\"type\":\"paypal\",\"id\":\"pp123\",\"email\":\"user@paypal.com\"}";

    PaymentMethod method = json.parse(json_content).getObject();

    assertThat(method).isInstanceOf(PayPal.class);
  }
}

Best Practices

  • Use @JsonTest for focused JSON testing
  • Test both serialization and deserialization
  • Test null handling and missing fields
  • Test nested and complex structures
  • Verify field name mapping with @JsonProperty
  • Test date/time formatting thoroughly
  • Test edge cases (empty strings, empty collections)

Common Pitfalls

  • Not testing null values
  • Not testing nested objects
  • Forgetting to test field name mappings
  • Not verifying JSON property presence/absence
  • Not testing deserialization of invalid JSON

Troubleshooting

JacksonTester not available: Ensure class is annotated with @JsonTest.

Field name doesn't match: Check @JsonProperty annotation and Jackson configuration.

DateTime parsing fails: Verify date format matches Jackson's expected format.

References

  • Spring @JsonTest Documentation
  • Jackson ObjectMapper
  • JSON Annotations
Repository
giuseppe-trisciuoglio/developer-kit
Last updated
Created

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.