安装
npx skills add https://github.com/giuseppe-trisciuoglio/developer-kit --skill unit-test-json-serialization
复制
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
Use @JsonTest annotation
Configure test context with @JsonTest for JacksonTester auto-configuration
Test both serialization and deserialization
Verify objects serialize to JSON and JSON deserializes to objects
Use JacksonTester
Autowire JacksonTester for type-safe JSON assertions
Test null handling
Verify null fields are handled correctly (included or excluded)
Test nested objects
Verify complex nested structures serialize/deserialize properly
Test date/time formats
Verify LocalDateTime, Date, and other temporal types format correctly
Test custom serializers
Verify @JsonSerialize and custom JsonSerializer implementations work
Use JsonPath assertions
Extract and verify specific JSON paths with JsonPath matchers
Examples
Setup: JSON Testing
Maven
<
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
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
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"
,
"alice@example.com"
,
25
)
;
org
.
assertj
.
core
.
data
.
Offset
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
)
.
isNotNull
(
)
.
hasFieldOrPropertyWithValue
(
"id"
,
1L
)
.
hasFieldOrPropertyWithValue
(
"name"
,
"Alice"
)
.
hasFieldOrPropertyWithValue
(
"email"
,
"alice@example.com"
)
.
hasFieldOrPropertyWithValue
(
"age"
,
25
)
;
}
@Test
void
shouldHandleNullFields
(
)
throws
Exception
{
String
json_content
=
"{\"id\":1,\"name\":null,\"email\":\"alice@example.com\",\"age\":null}"
;
UserDto
user
=
json
.
parse
(
json_content
)
.
getObject
(
)
;
assertThat
(
user
.
getName
(
)
)
.
isNull
(
)
;
assertThat
(
user
.
getAge
(
)
)
.
isNull
(
)
;
}
}
Testing Custom JSON Properties
@JsonProperty and @JsonIgnore
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
@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
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
@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
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
@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\":\"user@paypal.com\"}"
;
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
Jackson ObjectMapper
JSON Annotations
← 返回排行榜