// Install Skill
Install Skill
Skills are third-party code from public GitHub repositories. SkillHub scans for known malicious patterns but cannot guarantee safety. Review the source code before installing.
Install globally (user-level):
npx skillhub install giuseppe-trisciuoglio/developer-kit/unit-test-json-serializationInstall in current project:
npx skillhub install giuseppe-trisciuoglio/developer-kit/unit-test-json-serialization --projectskill.install.customTargetHelp
npx skillhub install giuseppe-trisciuoglio/developer-kit/unit-test-json-serialization --target-dir /path/to/skillsSuggested path: ~/.claude/skills/unit-test-json-serialization/
SKILL.md Content
---
name: unit-test-json-serialization
description: Provides patterns for unit testing JSON serialization/deserialization with Jackson and @JsonTest. Use when validating JSON mapping, custom serializers, and date format handling.
category: testing
tags: [junit-5, json-test, jackson, serialization, deserialization]
version: 1.0.1
allowed-tools: Read, Write, Bash, Glob, Grep
---
# Unit Testing JSON Serialization with @JsonTest
## Overview
This skill provides patterns for unit testing JSON serialization and deserialization using Spring's @JsonTest and Jackson. It covers testing POJO mapping, custom serializers/deserializers, field name mappings, nested objects, date/time formatting, polymorphic types, and null handling without full Spring context.
## When to Use
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
## Instructions
1. **Use @JsonTest annotation**: Configure test context with @JsonTest for JacksonTester auto-configuration
2. **Test both serialization and deserialization**: Verify objects serialize to JSON and JSON deserializes to objects
3. **Use JacksonTester**: Autowire JacksonTester for type-safe JSON assertions
4. **Test null handling**: Verify null fields are handled correctly (included or excluded)
5. **Test nested objects**: Verify complex nested structures serialize/deserialize properly
6. **Test date/time formats**: Verify LocalDateTime, Date, and other temporal types format correctly
7. **Test custom serializers**: Verify @JsonSerialize and custom JsonSerializer implementations work
8. **Use JsonPath assertions**: Extract and verify specific JSON paths with JsonPath matchers
## Examples
## Setup: JSON Testing
### Maven
```xml
<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
```kotlin
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
```java
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
```java
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
```java
@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
```java
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
```java
@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
```java
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
```java
@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
## Constraints and Warnings
- **@JsonTest loads limited context**: Only JSON-related beans are available; use @SpringBootTest for full context
- **Jackson version compatibility**: Ensure Jackson annotations match the Jackson version in use
- **Date format standards**: ISO-8601 is the default format; custom formats require @JsonFormat annotation
- **Null handling**: Use JsonInclude.Include.NON_NULL to exclude null fields from serialization
- **Circular references**: Be aware of circular references; use @JsonManagedReference/@JsonBackReference
- **Polymorphic type handling**: @JsonTypeInfo must be correctly configured for polymorphic deserialization
- **Immutable objects**: Use @JsonCreator and @JsonProperty for constructor-based deserialization
## 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/)
License
Declared license: MIT
MIT License
Copyright (c) 2026 giuseppe-trisciuoglio
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.View the license in the source repository — the version published there is authoritative.
// Install Skill
Install Skill
Skills are third-party code from public GitHub repositories. SkillHub scans for known malicious patterns but cannot guarantee safety. Review the source code before installing.
Install globally (user-level):
npx skillhub install giuseppe-trisciuoglio/developer-kit/unit-test-json-serializationInstall in current project:
npx skillhub install giuseppe-trisciuoglio/developer-kit/unit-test-json-serialization --projectskill.install.customTargetHelp
npx skillhub install giuseppe-trisciuoglio/developer-kit/unit-test-json-serialization --target-dir /path/to/skillsSuggested path: ~/.claude/skills/unit-test-json-serialization/