在使用Jackson处理对象序列化时,外部库提供的嵌入对象往往带有复杂的嵌套结构,默认序列化会导致JSON层级过深,不符合接口规范或存储需求,此时需要借助Jackson的高级特性实现扁平化序列化。
为什么需要扁平化外部库嵌入对象
很多第三方库定义的类包含多层嵌套结构,比如一个订单对象中嵌入了第三方物流库的物流信息对象,物流信息对象本身又嵌套了地址、联系人等子对象。如果直接使用Jackson默认序列化,得到的JSON会是多层嵌套的形式,既不利于前端解析,也不符合部分后端接口的参数要求。扁平化处理后,可以将嵌入对象的核心属性提取到外层,让结构更简洁。
基础实现方式:自定义JsonSerializer
自定义序列化器是最灵活的方式,能够完全控制序列化逻辑,适合处理结构复杂的外部库对象。核心思路是继承JsonSerializer类,重写serialize方法,手动将嵌入对象的属性写入JSON生成器。
示例场景说明
假设我们有一个业务层的Order类,其中嵌入了第三方库ThirdPartyLogistics类的实例,ThirdPartyLogistics类我们无法直接修改源码,其结构如下:
// 第三方库的嵌入对象,无法修改源码
public class ThirdPartyLogistics {
private String logisticsCode;
private String logisticsName;
private ContactInfo contactInfo;
// 构造方法、getter、setter省略
}
// 第三方库的联系人子对象
public class ContactInfo {
private String contactName;
private String contactPhone;
// 构造方法、getter、setter省略
}
我们的Order类定义如下:
public class Order {
private String orderId;
private Double totalAmount;
private ThirdPartyLogistics logistics;
// 构造方法、getter、setter省略
}
自定义序列化器实现
编写针对Order类的自定义序列化器,将ThirdPartyLogistics的属性扁平化到Order的序列化结果中:
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
public class OrderFlatSerializer extends JsonSerializer<Order> {
@Override
public void serialize(Order order, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeStartObject();
// 写入Order本身的属性
gen.writeStringField("orderId", order.getOrderId());
gen.writeNumberField("totalAmount", order.getTotalAmount());
// 处理嵌入的ThirdPartyLogistics对象,扁平化其属性
ThirdPartyLogistics logistics = order.getLogistics();
if (logistics != null) {
gen.writeStringField("logisticsCode", logistics.getLogisticsCode());
gen.writeStringField("logisticsName", logistics.getLogisticsName());
// 处理物流下的联系人子对象
ContactInfo contact = logistics.getContactInfo();
if (contact != null) {
gen.writeStringField("contactName", contact.getContactName());
gen.writeStringField("contactPhone", contact.getContactPhone());
}
}
gen.writeEndObject();
}
}
注册并使用自定义序列化器
通过@JsonSerialize注解将自定义序列化器绑定到Order类上,然后执行序列化:
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
// 绑定自定义序列化器
@JsonSerialize(using = OrderFlatSerializer.class)
public class Order {
private String orderId;
private Double totalAmount;
private ThirdPartyLogistics logistics;
// 构造方法、getter、setter省略
}
public class TestSerialize {
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
// 构造测试数据
ContactInfo contact = new ContactInfo();
contact.setContactName("张三");
contact.setContactPhone("13800138000");
ThirdPartyLogistics logistics = new ThirdPartyLogistics();
logistics.setLogisticsCode("SF1001");
logistics.setLogisticsName("顺丰速运");
logistics.setContactInfo(contact);
Order order = new Order();
order.setOrderId("ORD2024001");
order.setTotalAmount(199.99);
order.setLogistics(logistics);
// 执行序列化
String result = mapper.writeValueAsString(order);
System.out.println(result);
}
}
上述代码执行后得到的JSON结果如下,可以看到原本嵌套的物流和联系人属性都被扁平化到了外层:
{
"orderId": "ORD2024001",
"totalAmount": 199.99,
"logisticsCode": "SF1001",
"logisticsName": "顺丰速运",
"contactName": "张三",
"contactPhone": "13800138000"
}
使用@JsonUnwrapped注解的简化方式
如果外部库的嵌入对象结构相对简单,且Jackson版本在2.0以上,可以使用@JsonUnwrapped注解简化操作,不需要编写完整的自定义序列化器。
注解使用限制
需要注意的是,@JsonUnwrapped只能作用于当前项目的类中,无法直接修改外部库的类,因此需要在自己的业务类中,将需要扁平化的属性通过注解标记。如果外部库对象的属性需要全部扁平化,可以将嵌入对象的属性作为业务类的字段,再用@JsonUnwrapped标记。
代码示例
import com.fasterxml.jackson.annotation.JsonUnwrapped;
import com.fasterxml.jackson.databind.ObjectMapper;
public class SimpleOrder {
private String orderId;
private Double totalAmount;
// 使用@JsonUnwrapped将嵌入对象的属性扁平化
@JsonUnwrapped
private ThirdPartyLogistics logistics;
// 构造方法、getter、setter省略
}
public class TestUnwrapped {
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
// 构造和之前相同的测试数据
ContactInfo contact = new ContactInfo();
contact.setContactName("张三");
contact.setContactPhone("13800138000");
ThirdPartyLogistics logistics = new ThirdPartyLogistics();
logistics.setLogisticsCode("SF1001");
logistics.setLogisticsName("顺丰速运");
logistics.setContactInfo(contact);
SimpleOrder order = new SimpleOrder();
order.setOrderId("ORD2024001");
order.setTotalAmount(199.99);
order.setLogistics(logistics);
String result = mapper.writeValueAsString(order);
System.out.println(result);
}
}
这种方式得到的序列化结果和自定义序列化器类似,但如果嵌入对象还有嵌套的子对象,@JsonUnwrapped不会自动处理子对象的扁平化,此时还是需要结合自定义序列化器使用。
两种方式的对比与选择
我们可以通过下面的表格对比两种实现方式的特点,根据实际场景选择:
| 实现方式 | 灵活性 | 实现复杂度 | 适用场景 |
|---|---|---|---|
| 自定义JsonSerializer | 高,可完全控制序列化逻辑 | 较高,需要编写序列化器代码 | 嵌入对象结构复杂、需要自定义属性映射规则的场景 |
| @JsonUnwrapped注解 | 低,只能处理简单的扁平化需求 | 低,只需要添加注解 | 嵌入对象结构简单、不需要复杂映射的场景 |
注意事项
- 自定义序列化器时,要处理嵌入对象为null的情况,避免出现空指针异常。
- 如果扁平化的属性名称和Order本身的属性名称冲突,需要手动处理命名,比如添加前缀区分。
- 如果需要对序列化结果进行反序列化,还需要编写对应的自定义反序列化器,保证序列化反序列化逻辑一致。
扁平化序列化只改变输出的JSON结构,不会修改原有的Java对象结构,外部库的嵌入对象依然可以正常使用,不会影响其他业务逻辑。