🌏 閱讀中文版本
Why JSON Serialization Consistency Matters
1. Frontend Parsing and Type Safety
Inconsistent number types in API responses can cause serious frontend issues. For example:
- TypeScript type checking failures: Fields defined as
numberreceivestringvalues - Incorrect calculations:
"100" + 50results in"10050"instead of150 - Conditional logic errors:
"0" == falseis true, but"0" === falseis false
2. Cross-Environment Consistency
API responses should remain identical across development, testing, and production environments. Different serialization settings can lead to:
- Tests passing in staging but failing in production
- Mismatch between frontend mock data and actual API responses
- Data type conflicts when integrating with third-party systems
3. API Stability and Backward Compatibility
Once an API goes public, changing response formats breaks existing clients. Unified serialization settings ensure:
- Version upgrades don’t unexpectedly alter JSON format
- Data exchange between multiple services stays consistent
- Fewer client errors caused by type mismatches
Problem Background
When developing Spring Boot applications, we frequently serialize objects to JSON for data transmission or storage. However, JSON response data types may vary across different environments.
Typical problem scenario:
- Environment A: Java API serializes
Longnumbers as"12345"(string) - Environment B: Same field serializes as
12345(number)
This inconsistency can cause frontend parsing errors, TypeScript type check failures, or integration issues with other systems. Understanding and resolving this issue is crucial for maintaining system stability and consistency.
Jackson Serialization Configuration
Jackson is Spring Boot’s default JSON serialization/deserialization library. Its behavior can be adjusted through configuration files or code. Here are the key settings that affect how numeric data is represented:
Key Configuration Options
| Configuration Option | Default Value | Purpose | Impact |
|---|---|---|---|
WRITE_DATES_AS_TIMESTAMPS |
true |
Serialize dates as timestamps | Affects Date, LocalDateTime, etc. |
WRITE_NUMBERS_AS_STRINGS |
false |
Serialize numbers as strings | Affects Long, Integer, Double, etc. |
Important Note: Jackson’s default behavior serializes numbers to their native JSON types (e.g., Long becomes a JSON number), not strings. If Environment A produces string types, it means WRITE_NUMBERS_AS_STRINGS=true has been enabled there.
Configuration Methods
Method 1: application.properties Configuration
# Date serialization (false = ISO-8601 string format, true = Unix timestamp)
spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS=false
# Number serialization (false = numeric type, true = string type)
spring.jackson.serialization.WRITE_NUMBERS_AS_STRINGS=false
Method 2: application.yml Configuration
spring:
jackson:
serialization:
WRITE_DATES_AS_TIMESTAMPS: false # Use ISO-8601 format for dates
WRITE_NUMBERS_AS_STRINGS: false # Keep numbers in native type
Method 3: Custom ObjectMapper (Code Configuration)
Alternatively, you can customize ObjectMapper directly in Java code. This ensures identical Jackson configuration across all environments:
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class JacksonConfig {
@Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
// Serialize dates as ISO-8601 format instead of Unix timestamps
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
// Keep numbers in native type (Long, Integer, etc.) instead of strings
mapper.configure(SerializationFeature.WRITE_NUMBERS_AS_STRINGS, false);
return mapper;
}
}
Default Behavior and Overrides
Jackson Default Serialization Behavior
- Numeric types: Retain native type (
Long→ JSON number) - Date types: Serialize as Unix timestamp (
1672531200000)
How to Enable String Serialization for Numbers
To serialize numbers as strings (generally not recommended), explicitly enable:
spring.jackson.serialization.WRITE_NUMBERS_AS_STRINGS=true
Serialization result example:
{
"userId": "12345", // With WRITE_NUMBERS_AS_STRINGS enabled
"balance": "1000.50", // All numbers become strings
"timestamp": 1672531200000 // Dates remain timestamps (unless configured otherwise)
}
Best Practices
- Unify configuration across all environments
- Explicitly set values in
application.propertiesorapplication.yml - Avoid relying on defaults to ensure configuration visibility
- Explicitly set values in
- Version control your configuration files
- Include Jackson configuration in Git version control
- Ensure dev, test, and production use identical settings
- Prefer numeric types
- Keep
WRITE_NUMBERS_AS_STRINGS=false(default value) - Avoid unnecessary type conversion overhead
- Keep
- Use ISO-8601 format for dates
- Set
WRITE_DATES_AS_TIMESTAMPS=false - Improves readability and cross-system compatibility
- Set
- Validate with integration tests
- Write test cases to verify JSON serialization results
- Ensure types match API specifications
Frequently Asked Questions
Q1: Why does Environment A serialize as String while Environment B uses Long?
A: The most common reason is different application.properties settings between environments. Check the following:
- Environment A may have
WRITE_NUMBERS_AS_STRINGS=true - Environment B uses the default value (
false) - Or both environments use different versions of the Jackson library
Solution: Unify configuration files and ensure all environments use the same dependency versions.
Q2: What is the default value of WRITE_NUMBERS_AS_STRINGS?
A: The default is false, meaning Jackson serializes numbers to JSON numeric types, not strings. If your environment produces string serialization, it means this setting was explicitly changed to true.
Q3: How can I verify the current environment’s serialization settings?
A: You can check using the following approach:
@Autowired
private ObjectMapper objectMapper;
public void checkConfig() {
boolean numbersAsStrings = objectMapper.isEnabled(SerializationFeature.WRITE_NUMBERS_AS_STRINGS);
boolean datesAsTimestamps = objectMapper.isEnabled(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
System.out.println("WRITE_NUMBERS_AS_STRINGS: " + numbersAsStrings);
System.out.println("WRITE_DATES_AS_TIMESTAMPS: " + datesAsTimestamps);
}
Q4: Can I customize serialization for specific fields?
A: Yes, use @JsonSerialize or @JsonFormat annotations to customize serialization behavior for specific fields:
public class UserResponse {
@JsonSerialize(using = ToStringSerializer.class)
private Long userId; // Force serialize as string
@JsonFormat(shape = JsonFormat.Shape.NUMBER)
private Long balance; // Force serialize as number
}
Q5: How can I safely change settings in a running production environment?
A: We recommend a gradual rollout strategy:
- API versioning: Create a new API version (e.g.,
/v2/users) with the new serialization settings - Backward compatibility: Keep the old API version, giving clients time to migrate
- Gradual transition: Monitor error rates and confirm no impact before full cutover
- Documentation updates: Clearly inform API users of type changes
Q6: How can I test serialization results?
A: Write integration tests to verify JSON format:
@SpringBootTest
@AutoConfigureMockMvc
public class SerializationTest {
@Autowired
private MockMvc mockMvc;
@Test
public void testUserResponseSerialization() throws Exception {
mockMvc.perform(get("/api/users/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.userId").isNumber()) // Verify numeric type
.andExpect(jsonPath("$.balance").isNumber());
}
}
Troubleshooting
Issue: Set WRITE_NUMBERS_AS_STRINGS=false but still serializing as strings
Possible causes:
- Field uses
@JsonSerialize(using = ToStringSerializer.class)annotation - Custom
ObjectMapperbean overrides configuration file settings - Third-party library serializer being used
Solution: Check Java class annotations and confirm no other beans override ObjectMapper configuration.
Issue: Frontend receives numbers exceeding JavaScript safe integer range
Background: JavaScript’s Number.MAX_SAFE_INTEGER is 2^53 - 1 (approximately 9 quadrillion). Numbers exceeding this lose precision.
Solution: For large numbers (e.g., Twitter IDs, database BIGINT), serialize as strings:
public class TweetResponse {
@JsonSerialize(using = ToStringSerializer.class)
private Long tweetId; // Exceeds JavaScript safe integer range, use string
}
Summary
To maintain consistent JSON response data types across different environments, we recommend unifying Jackson serialization configuration in your Spring Boot application. By modifying configuration files or customizing ObjectMapper, you can effectively resolve issues where numeric data serializes differently across environments. This ensures system stability and consistency.
Core recommendations:
- Explicitly set
WRITE_NUMBERS_AS_STRINGS=false(keep numeric types) - Explicitly set
WRITE_DATES_AS_TIMESTAMPS=false(use ISO-8601 format) - Use identical configuration files across all environments
- Write integration tests to verify serialization results
- For numbers exceeding JavaScript safe integer range, use
@JsonSerializeannotation to force string serialization
Related Articles
- 解決 Spring Boot JSON 序列化問題:string vs long
- How to Troubleshoot and Resolve Spring Boot Configuration Default Value Issues
- Quartz Clustering Complete Guide: Preventing Duplicate Task Execution in ECS Multi-Container Environments
- Multiple Methods for Image Upload in Java Web Applications
- AWS Outage Deep Dive: Multi-Cloud Disaster Recovery Strategies for Architects