Provides patterns for unit testing JSON serialization/deserialization using Jackson and @JsonTest in Spring Boot apps. Validates POJOs, custom serializers, dates, and polymorphic types.
From developer-kit-javanpx claudepluginhub giuseppe-trisciuoglio/developer-kit --plugin developer-kit-javaThis skill is limited to using the following tools:
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Enables AI agents to execute x402 payments with per-task budgets, spending controls, and non-custodial wallets via MCP tools. Use when agents pay for APIs, services, or other agents.
@JsonTestProvides 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.
@JsonProperty, @JsonIgnore, and field name mappings@JsonTest → Enables JacksonTester auto-configurationjson.write(object) and assert JSON paths with extractingJsonPath*json.parse(json) or json.parseObject(json) and assert object state<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>
dependencies {
implementation("org.springframework.boot:spring-boot-starter-json")
testImplementation("org.springframework.boot:spring-boot-starter-test")
}
@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();
}
}
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");
}
}
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);
}
}
@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");
}
}
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");
}
}
@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);
}
}
@JsonIncludeextractingJsonPath* methods for precise field assertions@JsonTest loads limited context: Only JSON-related beans; use @SpringBootTest for full Spring context@JsonFormat for custom patterns@JsonInclude(Include.NON_NULL) to exclude nulls from serialization@JsonManagedReference/@JsonBackReference to prevent infinite loops@JsonCreator + @JsonProperty for constructor-based deserialization@JsonTypeInfo must correctly identify the subtype for deserialization to workWhen 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).