jackson-kotlin-serialization-specialist
โดย kotlin
Diagnose and design JSON serialization and deserialization behavior for Kotlin plus Jackson in Spring applications. Use when DTOs fail to deserialize, default…
npx skills add https://github.com/kotlin/kotlin-backend-agent-skills --skill jackson-kotlin-serialization-specialistJackson Kotlin Serialization Specialist
Source mapping: Tier 2 high-value skill derived from Kotlin_Spring_Developer_Pipeline.md (SK-08).
Mission
Make Kotlin plus Jackson behavior explicit, compatible, and testable. Treat wire-format correctness as a contract problem, not only a mapper-configuration problem.
Read First
- The actual DTO or event model classes.
- The exact failing JSON payload or expected payload examples.
ObjectMappercustomizers, Spring Boot Jackson properties, and any per-client mapper overrides.- Build files to verify Jackson module alignment with Spring Boot and Kotlin versions.
- The boundary where serialization matters: MVC, WebFlux, Kafka, Redis, persistence JSON column, or external HTTP client.
Diagnose In This Order
- Verify module presence and alignment:
jackson-module-kotlinJavaTimeModule- other custom modules or serializers
- Verify constructor semantics:
- default parameters
- required parameters
- nullable versus non-null
- Verify field presence semantics:
- absent
- present with
null - present with value
- Verify naming, inclusion, and date-time strategy.
- Verify polymorphism or custom serializer behavior.
- Verify whether the real bug comes from a local mapper override rather than the global mapper.
Core Kotlin Rules
- Keep DTOs immutable unless the project already has a strong alternative convention.
- Do not switch
valtovarjust to appease Jackson. - Do not add empty constructors to Kotlin DTOs as a workaround if the Kotlin module can model the contract correctly.
- Treat nullable as a wire-contract decision, not a convenient escape hatch.
- Be explicit about value classes, sealed hierarchies, and default parameter behavior.
Advanced Serialization Traps
- Missing field and explicit
nullare not the same. For PATCH-like contracts, model tri-state semantics deliberately. - Default constructor values can silently hide client mistakes if the field should have been required.
@JsonIncludemay improve payload size but can also erase signal that clients rely on.- Non-null primitives,
FAIL_ON_NULL_FOR_PRIMITIVES, and Kotlin non-null types interact differently across payload shapes. - Sealed classes need stable, versionable type discriminators. Do not treat polymorphic type ids as an internal detail once they are on the wire.
- Enum serialization by name, code, or custom object form is a public compatibility choice.
- Date-time serialization must make timezone assumptions explicit.
Instant,OffsetDateTime, andLocalDateTimeare not interchangeable. @JvmInline value classsupport may differ by Jackson version and serializer context. Verify scalar form and map-key behavior explicitly.- Global
ObjectMapperchanges can break unrelated endpoints or message consumers. Prefer narrow fixes when the issue is boundary-specific.
Boundary-Specific Nuances
- MVC request and response mapping, Kafka message mapping, Redis payload mapping, and JSON-column mapping often use different mapper lifecycles even inside one codebase.
ObjectMapper.copy()can preserve most configuration while still drifting from future global changes. If a subsystem owns a private mapper, document that divergence.- Kotlin default parameters interact differently with creator annotations, mix-ins, and custom deserializers. If custom deserialization exists, verify constructor invocation explicitly.
- Unknown enum handling, unknown-property handling, and coercion rules are compatibility decisions. A permissive setting may preserve old clients or may quietly accept garbage.
- If the API is documented through OpenAPI or consumer contracts, make sure the documented nullability and actual wire behavior match. Kotlin type hints alone are not enough.
Expert Heuristics
- If the same model is used on both inbound and outbound boundaries, check whether the optimal serializer settings are actually symmetrical. Often they are not.
- If clients rely on partial update semantics, prefer an explicit patch model rather than trying to infer intent from ordinary DTO nullability.
- If a serializer bug appears after a dependency upgrade, inspect feature defaults and module registration order before rewriting DTOs.
- If compatibility matters, prove the fix with golden JSON examples or snapshot-style serialization tests, not only with one happy-path request.
Design Rules
- Choose one naming strategy and document it.
- Keep transport DTOs separate from persistence and domain objects when contract stability matters.
- If payload evolution matters, favor additive fields and backward-compatible defaults over silent semantic changes.
- If multiple serialization contexts exist, decide which behavior is global and which is boundary-specific.
Output Contract
Return these sections:
Observed behavior: what the current mapper does.Contract expectation: what the wire format should mean.Root cause: module, DTO, annotation, or mapper configuration issue.Minimal fix: the smallest safe code or config change.Compatibility risk: what existing clients or consumers might notice.Verification: tests or sample payloads that prove the behavior.
Guardrails
- Do not recommend random Jackson versions outside the repository's version authority.
- Do not add global mapper behavior for a local one-off issue without explaining blast radius.
- Do not hide a contract problem behind broad
JsonNodeorMap<String, Any>usage unless the boundary is intentionally untyped. - Do not rely on Jackson defaults when a stable external contract matters.
Quality Bar
A good run of this skill explains the wire contract, the mapper mechanics, and the compatibility impact in one coherent answer. A bad run sprinkles annotations until the example payload passes while leaving the contract ambiguous or unstable.