在日常开发中,我们经常遇到标准JSON格式无法满足需求的场景。就拿电商系统来说,订单状态可能包含状态码、中文描述、国际化Key等多个属性,但前端只需要一个简单的状态标识。这时候,Jackson默认的序列化方式就显得力不从心了。
我遇到过这样一个实际案例:一个订单状态枚举原本设计为包含code、desc、i18nKey三个字段,但前端同事坚持要求接口返回简单的字符串。如果直接使用Jackson的默认序列化,前端会收到一个复杂的嵌套对象,这显然不符合预期。
java复制public enum OrderStatus {
PAID(1001, "已支付", "order.status.paid"),
SHIPPED(1002, "已发货", "order.status.shipped");
private int code;
private String desc;
private String i18nKey;
// 构造方法和getter省略
}
默认序列化结果会是这样的:
json复制{
"status": {
"code": 1001,
"desc": "已支付",
"i18nKey": "order.status.paid"
}
}
而前端期望的是:
json复制{
"status": "paid"
}
这种场景下,@JsonSerialize和@JsonDeserialize就派上用场了。它们允许我们完全控制某个字段的序列化和反序列化过程,实现标准JSON格式无法满足的特殊需求。
@JsonSerialize注解用于指定自定义的序列化逻辑。它的核心参数是using,需要传入一个继承自JsonSerializer的自定义序列化器类。
让我们改造前面的订单状态例子。首先需要创建一个自定义序列化器:
java复制public class OrderStatusSerializer extends JsonSerializer<OrderStatus> {
@Override
public void serialize(OrderStatus value, JsonGenerator gen, SerializerProvider provider)
throws IOException {
// 将枚举值转换为简单字符串
String statusStr = value.name().toLowerCase();
gen.writeString(statusStr);
}
}
然后在枚举字段上应用这个序列化器:
java复制public class Order {
@JsonSerialize(using = OrderStatusSerializer.class)
private OrderStatus status;
// 其他字段和方法省略
}
现在序列化结果就会变成前端期望的简单字符串形式了。
除了简单的类型转换,自定义序列化器还能处理更复杂的场景。比如我们需要根据不同的环境返回不同的日期格式:
java复制public class EnvAwareDateSerializer extends JsonSerializer<Date> {
@Override
public void serialize(Date value, JsonGenerator gen, SerializerProvider provider)
throws IOException {
String env = System.getProperty("app.env", "prod");
SimpleDateFormat format;
if ("dev".equals(env)) {
format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
} else {
format = new SimpleDateFormat("yyyy-MM-dd");
}
gen.writeString(format.format(value));
}
}
这个序列化器会根据系统属性app.env的值决定使用详细还是简化的日期格式,非常适合多环境下的差异化需求。
@JsonDeserialize是@JsonSerialize的逆过程,用于自定义反序列化逻辑。它需要指定一个继承自JsonDeserializer的实现类。
继续我们的订单状态例子,前端传过来的"paid"字符串需要转换回OrderStatus枚举:
java复制public class OrderStatusDeserializer extends JsonDeserializer<OrderStatus> {
@Override
public OrderStatus deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException {
String text = p.getText();
// 将字符串转换为大写后匹配枚举值
return OrderStatus.valueOf(text.toUpperCase());
}
}
应用方式与序列化类似:
java复制public class Order {
@JsonDeserialize(using = OrderStatusDeserializer.class)
private OrderStatus status;
}
有时候我们需要处理更复杂的反序列化逻辑。比如一个支持多种输入格式的日期字段:
java复制public class FlexibleDateDeserializer extends JsonDeserializer<Date> {
private static final String[] FORMATS = {
"yyyy-MM-dd",
"yyyy/MM/dd",
"MM/dd/yyyy",
"yyyy-MM-dd HH:mm:ss"
};
@Override
public Date deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException {
String dateStr = p.getText();
for (String format : FORMATS) {
try {
return new SimpleDateFormat(format).parse(dateStr);
} catch (ParseException e) {
// 尝试下一种格式
}
}
throw new RuntimeException("无法解析日期: " + dateStr);
}
}
这个反序列化器会依次尝试多种日期格式,直到找到匹配的为止,大大提高了接口的容错性。
现在我们把序列化和反序列化结合起来,实现一个完整的订单状态处理方案。首先定义我们的订单类:
java复制public class Order {
private Long id;
@JsonSerialize(using = OrderStatusSerializer.class)
@JsonDeserialize(using = OrderStatusDeserializer.class)
private OrderStatus status;
// 其他字段和方法
}
序列化器实现:
java复制public class OrderStatusSerializer extends JsonSerializer<OrderStatus> {
@Override
public void serialize(OrderStatus value, JsonGenerator gen, SerializerProvider provider)
throws IOException {
Map<String, Object> statusMap = new HashMap<>();
statusMap.put("code", value.getCode());
statusMap.put("display", value.getDesc());
statusMap.put("key", value.getI18nKey());
gen.writeObject(statusMap);
}
}
反序列化器实现:
java复制public class OrderStatusDeserializer extends JsonDeserializer<OrderStatus> {
@Override
public OrderStatus deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException {
JsonNode node = p.getCodec().readTree(p);
int code = node.get("code").asInt();
for (OrderStatus status : OrderStatus.values()) {
if (status.getCode() == code) {
return status;
}
}
throw new IllegalArgumentException("无效的状态码: " + code);
}
}
让我们写个测试用例验证我们的实现:
java复制public class OrderTest {
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
// 测试序列化
Order order = new Order();
order.setId(1L);
order.setStatus(OrderStatus.PAID);
String json = mapper.writeValueAsString(order);
System.out.println("序列化结果: " + json);
// 测试反序列化
String inputJson = "{\"id\":2,\"status\":{\"code\":1002,\"display\":\"已发货\",\"key\":\"order.status.shipped\"}}";
Order parsedOrder = mapper.readValue(inputJson, Order.class);
System.out.println("反序列化结果: " + parsedOrder.getStatus());
}
}
输出结果应该显示序列化和反序列化都正常工作,完美实现了我们的需求。
为了提高性能,序列化器实例应该设计为无状态的,这样Jackson就可以重用它们。避免在序列化器中保存可变状态,如果需要配置参数,可以通过构造函数传入并保存为final字段。
java复制public class ConfigurableDateSerializer extends JsonSerializer<Date> {
private final String dateFormat;
public ConfigurableDateSerializer(String dateFormat) {
this.dateFormat = dateFormat;
}
@Override
public void serialize(Date value, JsonGenerator gen, SerializerProvider provider)
throws IOException {
gen.writeString(new SimpleDateFormat(dateFormat).format(value));
}
}
默认情况下,序列化器需要自己处理null值。可以通过@JsonInclude注解或者在序列化器中显式处理:
java复制@Override
public void serialize(OrderStatus value, JsonGenerator gen, SerializerProvider provider)
throws IOException {
if (value == null) {
gen.writeNull();
return;
}
// 正常处理逻辑
}
在Spring Boot项目中,我们可以通过配置全局的ObjectMapper来应用自定义序列化器:
java复制@Configuration
public class JacksonConfig {
@Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(OrderStatus.class, new OrderStatusSerializer());
module.addDeserializer(OrderStatus.class, new OrderStatusDeserializer());
mapper.registerModule(module);
return mapper;
}
}
这样就不需要在每个字段上都添加注解,特别适合处理系统级别的通用转换逻辑。