APIs · Lesson 3
JSON Serialization with Jackson
What is JSON Serialization?
Serialization converts Java objects into a format suitable for storage or transmission (like JSON). Deserialization reverses the process, converting JSON back into Java objects. This is fundamental for REST APIs — clients send/receive JSON; your Java code works with objects. Libraries like Jackson and Gson automate this conversion, handling nested objects, arrays, and custom types.
Jackson: The Standard JSON Library
Jackson is the most widely used JSON library in Java. Spring Boot uses Jackson by default. It automatically discovers object properties and converts them to/from JSON. You can customize serialization with annotations like @JsonProperty, @JsonIgnore, and @JsonFormat.
import com.fasterxml.jackson.annotation.*;
import java.util.Date;
import java.util.List;
// User class with Jackson annotations
@JsonIgnoreProperties(ignoreUnknown = true) // Ignore unknown JSON fields
public class User {
@JsonProperty("user_id") // JSON field name differs from Java field name
private int id;
private String name;
@JsonProperty(required = true) // Field is required in JSON
private String email;
@JsonIgnore // Don't serialize password to JSON
private String password;
@JsonFormat(pattern = "yyyy-MM-dd") // Format date in JSON
private Date createdAt;
@JsonInclude(JsonInclude.Include.NON_NULL) // Only include non-null fields
private String bio;
private List<String> tags; // Lists are automatically serialized
// Constructor, getters, setters...
public User() {}
public User(int id, String name, String email) {
this.id = id;
this.name = name;
this.email = email;
}
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public Date getCreatedAt() { return createdAt; }
public void setCreatedAt(Date createdAt) { this.createdAt = createdAt; }
public String getBio() { return bio; }
public void setBio(String bio) { this.bio = bio; }
public List<String> getTags() { return tags; }
public void setTags(List<String> tags) { this.tags = tags; }
}
// Spring Boot automatically serializes/deserializes with Jackson
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/users")
public class UserController {
// GET returns User as JSON
@GetMapping("/{id}")
public User getUser(@PathVariable int id) {
User user = new User(1, "Alice", "alice@example.com");
user.setCreatedAt(new Date());
user.setTags(java.util.Arrays.asList("java", "spring"));
return user;
// Automatically serialized to JSON:
// {
// "user_id": 1,
// "name": "Alice",
// "email": "alice@example.com",
// "createdAt": "2023-01-15",
// "tags": ["java", "spring"]
// }
// Note: password and null bio are excluded
}
// POST accepts JSON and deserializes to User object
@PostMapping
public User createUser(@RequestBody User user) {
// Spring Boot automatically deserializes JSON to User object
System.out.println("Created user: " + user.getName());
return user;
}
}
Custom Serialization with JsonSerialize and JsonDeserialize
For complex types (enums, custom objects), implement custom serializers and deserializers.
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.annotation.*;
import java.io.IOException;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.Date;
// Custom serializer for Price (BigDecimal with 2 decimals)
class PriceSerializer extends JsonSerializer<BigDecimal> {
@Override
public void serialize(BigDecimal value, JsonGenerator gen, SerializerProvider provider) throws IOException {
if (value == null) {
gen.writeNull();
} else {
gen.writeString(String.format("$%.2f", value)); // Format as "$99.99"
}
}
}
// Custom deserializer for Price
class PriceDeserializer extends JsonDeserializer<BigDecimal> {
@Override
public BigDecimal deserialize(JsonParser p, DeserializationContext ctx) throws IOException {
String value = p.getValueAsString();
if (value == null || value.isEmpty()) {
return null;
}
// Remove $ and convert to BigDecimal
return new BigDecimal(value.replace("$", ""));
}
}
// Product class with custom price serialization
class Product {
private int id;
private String name;
@JsonSerialize(using = PriceSerializer.class)
@JsonDeserialize(using = PriceDeserializer.class)
private BigDecimal price;
// Getters, setters...
public Product() {}
public Product(int id, String name, BigDecimal price) {
this.id = id;
this.name = name;
this.price = price;
}
public int getId() { return id; }
public String getName() { return name; }
public BigDecimal getPrice() { return price; }
public void setPrice(BigDecimal price) { this.price = price; }
}
// Usage
import com.fasterxml.jackson.databind.ObjectMapper;
class JsonSerializationExample {
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
// Java to JSON (serialization)
Product product = new Product(1, "Laptop", new BigDecimal("999.99"));
String json = mapper.writeValueAsString(product);
System.out.println(json); // {"id":1,"name":"Laptop","price":"$999.99"}
// JSON to Java (deserialization)
String jsonInput = "{\"id\":2,\"name\":\"Phone\",\"price\":\"$599.99\"}";
Product product2 = mapper.readValue(jsonInput, Product.class);
System.out.println(product2.getPrice()); // 599.99 (as BigDecimal)
}
}
Collections and Nested Objects
Jackson handles lists, maps, and nested objects automatically.
import java.util.List;
import java.util.Map;
class Order {
private int id;
private String status;
private List<OrderItem> items; // List of nested objects
private Map<String, String> metadata; // Key-value pairs
public Order() {}
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
public List<OrderItem> getItems() { return items; }
public void setItems(List<OrderItem> items) { this.items = items; }
public Map<String, String> getMetadata() { return metadata; }
public void setMetadata(Map<String, String> metadata) { this.metadata = metadata; }
}
class OrderItem {
private String productId;
private int quantity;
private double price;
public OrderItem() {}
public OrderItem(String productId, int quantity, double price) {
this.productId = productId;
this.quantity = quantity;
this.price = price;
}
public String getProductId() { return productId; }
public int getQuantity() { return quantity; }
public double getPrice() { return price; }
}
// Serialization example
class CollectionsExample {
public static void main(String[] args) throws Exception {
// Create order with nested items
Order order = new Order();
order.setId(1001);
order.setStatus("PENDING");
order.setItems(java.util.Arrays.asList(
new OrderItem("PROD-001", 2, 49.99),
new OrderItem("PROD-002", 1, 99.99)
));
order.setMetadata(java.util.Map.of(
"currency", "USD",
"shippingMethod", "EXPRESS"
));
// Convert to JSON
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(order);
System.out.println(json);
// JSON output:
// {
// "id" : 1001,
// "status" : "PENDING",
// "items" : [ {
// "productId" : "PROD-001",
// "quantity" : 2,
// "price" : 49.99
// }, {
// "productId" : "PROD-002",
// "quantity" : 1,
// "price" : 99.99
// } ],
// "metadata" : {
// "currency" : "USD",
// "shippingMethod" : "EXPRESS"
// }
// }
}
}
| Jackson Annotation | Purpose | Example |
|---|---|---|
| @JsonProperty | Rename JSON field | @JsonProperty("user_id") on field id |
| @JsonIgnore | Exclude from JSON | @JsonIgnore on password field |
| @JsonIgnoreProperties | Ignore multiple fields | @JsonIgnoreProperties({"password", "token"}) |
| @JsonFormat | Format dates/numbers | @JsonFormat(pattern = "yyyy-MM-dd") |
| @JsonInclude | Include/exclude null/empty | @JsonInclude(JsonInclude.Include.NON_NULL) |
| @JsonSerialize | Custom serializer | @JsonSerialize(using = PriceSerializer.class) |
| @JsonDeserialize | Custom deserializer | @JsonDeserialize(using = PriceDeserializer.class) |
Common Mistake: Forgetting
@JsonPropertywhen JSON field names differ from Java field names (API uses snake_case, Java uses camelCase). Also, exposing sensitive fields (passwords, tokens) in JSON — use@JsonIgnoreon fields that shouldn't serialize. Don't serialize large nested objects unnecessarily; flatten complex structures into DTOs (Data Transfer Objects) for JSON.
Best Practices for JSON Serialization
- Use
@JsonPropertywhen JSON and Java field names differ - Use
@JsonIgnoreon sensitive fields (passwords, tokens, internal IDs) - Flatten complex objects for JSON — create DTOs for API responses
- Use
@JsonFormatfor consistent date/time formatting - Handle null values with
@JsonIncludeto reduce payload size - Implement custom serializers only for complex types
- Version your JSON schema if you plan API changes
- Use generic ObjectMapper configuration in Spring Boot's context for consistency
Real-World Example: User Registration API
A user registration endpoint accepts JSON: POST /api/users with {"name": "Alice", "email": "alice@example.com", "password": "secret"}. Jackson deserializes to User object. The endpoint validates email, hashes password, saves to database. The response excludes password: {"user_id": 1, "name": "Alice", "email": "alice@example.com", "createdAt": "2023-01-15"}. Date is formatted via @JsonFormat. If JSON contains unknown fields, they're ignored (via @JsonIgnoreProperties). Lists and nested objects (like addresses) are automatically serialized. By leveraging Jackson's annotations, the API is clean, secure, and easy to maintain.
Quiz Check