unit-test-json-serialization
Install this skill
npx skills add giuseppe-trisciuoglio/developer-kitWorks across Claude Code, Cursor, Codex, Copilot & Antigravity
The @JsonTest annotation in Spring Boot provides a streamlined environment for validating Jackson-based object mapping without loading the entire application context. It initializes the JacksonTester utility, which wraps standard serialization and deserialization processes. This approach is highly efficient for unit testing Data Transfer Objects (DTOs), ensuring that custom annotations like @JsonProperty and @JsonIgnore are respected during runtime conversions. By focusing strictly on the JSON layer, developers can confirm that field naming, data types, and complex structures behave as expected during API interactions. This method is the preferred way to catch mapping regressions before they manifest as integration errors in your REST controllers or service layers.
When to Use This Skill
- •Validating DTO mapping during build-time tests
- •Checking edge cases for null values in API requests
- •Ensuring custom serialization logic functions correctly
- •Testing backwards compatibility of JSON response structures
How to Invoke This Skill
Example prompts that trigger this skill in Claude Code, Cursor, or Antigravity:
- “How to test Jackson serialization with @JsonTest
- “Validate DTO JSON mapping in Spring Boot
- “Test custom field name mapping in my Java object
- “Asserting JSON structure in unit tests
- “JacksonTester example for unit testing classes
Pro Tips
- 💡Combine `@JsonTest` with `assertj-json` for more fluent and readable JSON assertion syntax.
- 💡Utilize `ObjectMapper` configuration (e.g., `SerializationFeature.INDENT_OUTPUT`) within tests to pretty-print JSON for easier debugging of failures.
- 💡Always test edge cases such as null values, empty collections, and maximum length strings to ensure robust serialization/deserialization.
What this skill does
- •Verify POJO to JSON string conversion accuracy
- •Validate incoming JSON payload parsing into Java objects
- •Test behavior of custom Jackson annotations on fields
- •Inspect specific JSON path nodes for value matching
- •Handle serialization of nested lists and complex object graphs
When not to use it
- ✕Testing end-to-end HTTP request flows including headers
- ✕Verifying business logic integration or database persistence
Example workflow
- Add spring-boot-starter-json to build configuration
- Annotate the test class with @JsonTest
- Inject JacksonTester into the test instance
- Invoke json.write(pojo) for serialization assertions
- Use json.parse(string).getObject() for deserialization verification
- Perform assertions using AssertJ against the parsed fields
Prerequisites
- –Spring Boot project structure
- –JUnit 5 and AssertJ libraries
- –Jackson Databind dependency
Pitfalls & limitations
- !Over-testing by using @JsonTest for logic that requires a full service layer
- !Failing to account for Jackson module configurations not present in the slice test
- !Misinterpreting JSON path syntax in AssertJ assertions
FAQ
How it compares
While manual testing involves writing boilerplate ObjectMapper configuration and handling checked exceptions, @JsonTest abstracts this into a clean, assertion-focused API that integrates directly with Spring's test lifecycle.
📄 Full skill instructions — original source: giuseppe-trisciuoglio/developer-kit
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", "[email protected]", 25);
org.assertj.core.data.Offset result = json.write(user);
result
.extractingJsonPathNumberValue("$.id").isEqualTo(1)
.extractingJsonPathStringValue("$.name").isEqualTo("Alice")
.extractingJsonPathStringValue("$.email").isEqualTo("[email protected]")
.extractingJsonPathNumberValue("$.age").isEqualTo(25);
}
@Test
void shouldDeserializeJsonToUser() throws Exception {
String json_content = "{\"id\":1,\"name\":\"Alice\",\"email\":\"[email protected]\",\"age\":25}";
UserDto user = json.parse(json_content).getObject();
assertThat(user)
.isNotNull()
.hasFieldOrPropertyWithValue("id", 1L)
.hasFieldOrPropertyWithValue("name", "Alice")
.hasFieldOrPropertyWithValue("email", "[email protected]")
.hasFieldOrPropertyWithValue("age", 25);
}
@Test
void shouldHandleNullFields() throws Exception {
String json_content = "{\"id\":1,\"name\":null,\"email\":\"[email protected]\",\"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\":\"[email protected]\"}";
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](https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/autoconfigure/json/JsonTest.html)
- [Jackson ObjectMapper](https://fasterxml.github.io/jackson-databind/javadoc/2.15/com/fasterxml/jackson/databind/ObjectMapper.html)
- [JSON Annotations](https://fasterxml.github.io/jackson-annotations/javadoc/2.15/)
How to Use This Skill Unit
Option A: Project-Specific (Recommended)
- Click "Download" above
- In your project, create the directory:
.agent/skills/unit-test-json-serialization/ - Save the file as
SKILL.md - The agent will automatically discover the skill based on its description.
Option B: Global Installation (All Agents)
Save the file to these locations to make it available across all projects:
- Claude Code:
~/.claude/skills/giuseppe-trisciuoglio/developer-kit/unit-test-json-serialization/SKILL.md - Cursor:
~/.cursor/skills/giuseppe-trisciuoglio/developer-kit/unit-test-json-serialization/SKILL.md - Antigravity:
~/.gemini/antigravity/skills/giuseppe-trisciuoglio/developer-kit/unit-test-json-serialization/SKILL.md
🚀 Install with CLI:npx skills add giuseppe-trisciuoglio/developer-kit