plugins/developer-kit-java/skills/unit-test-json-serialization/SKILL.md
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.
npx skillsauth add giuseppe-trisciuoglio/developer-kit unit-test-json-serializationInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
@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", "[email protected]", 25);
JsonContent<UserDto> 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.getId()).isEqualTo(1L);
assertThat(user.getName()).isEqualTo("Alice");
assertThat(user.getEmail()).isEqualTo("[email protected]");
assertThat(user.getAge()).isEqualTo(25);
}
@Test
void shouldHandleNullFields() throws Exception {
String json_content = "{\"id\":1,\"name\":null,\"email\":\"[email protected]\"}";
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).
@JsonTest Documentationdevelopment
Explore codebase before committing to a change. Phase executor skill for specs.explore command.
development
Executes real end-to-end verification against a running application after specification implementation. Detects the application type, starts the local runtime (Docker, Node, Spring Boot, etc.), runs real tests (curl for REST APIs, Playwright for web SPAs, computer-use for desktop apps), verifies acceptance criteria from the functional specification, generates a markdown report, and tears down the environment. Use when: user asks to verify a completed spec with real tests, run e2e checks after implementation, validate acceptance criteria in a live environment, or test the feature for real after task completion.
development
Initialize Spec-Driven Development context — detects tech stack, conventions, architecture patterns, and bootstraps persistence backends. Triggers on 'sdd-init', 'init sdd', 'setup sdd', 'initialize sdd', 'setup project', 'initialize project context'. Creates/updates docs/specs/architecture.md & ontology.md (Constitution), and populates knowledge-graph.json.
development
Optimizes raw idea descriptions into structured prompts ready for the brainstorming workflow. TRIGGER when: user says "optimize for brainstorm", "prepare idea for brainstorm", "enhance this idea", "make this ready for brainstorming", "imposta per brainstorm", or wants to improve a feature idea before using /specs.brainstorm. DO NOT TRIGGER for code optimization, refactoring, or general prompt engineering tasks.