Developer-kit unit-test-json-serialization
Provides patterns for unit testing JSON serialization/deserialization with Jackson and `@JsonTest`. Validates JSON mapping, custom serializers, date formats, and polymorphic types. Use when testing JSON serialization, validating custom serializers, or writing JSON unit tests in Spring Boot applications.
install
source · Clone the upstream repo
git clone https://github.com/giuseppe-trisciuoglio/developer-kit
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/giuseppe-trisciuoglio/developer-kit "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/developer-kit-java/skills/unit-test-json-serialization" ~/.claude/skills/giuseppe-trisciuoglio-developer-kit-unit-test-json-serialization && rm -rf "$T"
manifest:
plugins/developer-kit-java/skills/unit-test-json-serialization/SKILL.mdsource content
Unit Testing JSON Serialization with @JsonTest
@JsonTestOverview
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
, and field name mappings@JsonIgnore - Testing date/time format handling (LocalDateTime, Date)
- Testing null handling and missing fields
- Testing polymorphic type deserialization
Instructions
- Annotate test class with
→ Enables JacksonTester auto-configuration@JsonTest - Autowire JacksonTester for target type → Provides type-safe JSON assertions
- Test serialization → Call
and assert JSON paths withjson.write(object)extractingJsonPath* - Test deserialization → Call
orjson.parse(json)
and assert object statejson.parseObject(json) - 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
methods for precise field assertionsextractingJsonPath* - 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
loads limited context: Only JSON-related beans; use@JsonTest
for full Spring context@SpringBootTest- Jackson version: Ensure annotation versions match the Jackson version in use
- Date formats: ISO-8601 is default; use
for custom patterns@JsonFormat - Null handling: Use
to exclude nulls from serialization@JsonInclude(Include.NON_NULL) - Circular references: Use
/@JsonManagedReference
to prevent infinite loops@JsonBackReference - Immutable objects: Use
+@JsonCreator
for constructor-based deserialization@JsonProperty - Polymorphic types:
must correctly identify the subtype for deserialization to work@JsonTypeInfo
Debugging Workflow
When a JSON test fails, follow this workflow:
| Failure Symptom | Common Cause | How to Verify |
|---|---|---|
assertion fails | Field name mismatch | Check spelling matches JSON key |
| Null expected but got value | configured | Verify annotation on field/class |
| Deserialization returns wrong type | Missing | Add type info property to JSON or configure subtype mapping |
| Date format mismatch | Format string incorrect | Confirm matches expected string |
| Missing field in output | or transient modifier | Check field for or keyword |
| Nested object is null | Inner JSON missing or malformed | Log parsed JSON; verify inner structure matches POJO |
| 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).