如何用Jackson实现POJO非空属性深拷贝及嵌套对象通用合并
Great question! I've run into this exact issue with Jackson's updateValue behavior before—here's a clean way to handle nested merging globally without having to configure each POJO individually:
The core problem you're facing is that:
- Default
updateValuereplaces entire nested objects instead of merging their properties - Global
setDefaultMergeable(true)allows merging, but also lets null values from the source overwrite non-null values in the destination - Configuring
setMergeable(true)for each nested class isn't scalable with many POJOs
Fix: Custom BeanDeserializerModifier for Global Merging + Null Skipping
We can create a custom BeanDeserializerModifier to enable merging for all your POJO classes, while keeping the Nulls.SKIP rule to prevent null source values from overwriting destination data.
Step 1: Implement the BeanDeserializerModifier
import com.fasterxml.jackson.databind.BeanDescription; import com.fasterxml.jackson.databind.DeserializationConfig; import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.deser.BeanDeserializerBuilder; public class GlobalMergeableModifier extends BeanDeserializerModifier { @Override public BeanDeserializerBuilder updateBuilder( DeserializationConfig config, BeanDescription beanDesc, BeanDeserializerBuilder builder) { // Enable merging for all your application POJOs (adjust the condition as needed) if (!beanDesc.getBeanClass().isPrimitive() && !beanDesc.getBeanClass().getPackageName().startsWith("java.")) { builder.setMergeable(true); } return super.updateBuilder(config, beanDesc, builder); } }
(Note: The condition here excludes primitive types and JDK classes—tweak it to match your project's POJO criteria, like checking for a custom annotation or specific package prefix.)
Step 2: Configure ObjectMapper with the Modifier + Null Skipping
ObjectMapper mapper = new ObjectMapper(); // Skip setting null values from source to destination mapper.setDefaultSetterInfo(JsonSetter.Value.forValueNulls(Nulls.SKIP)); // Register the custom modifier to enable global merging for your POJOs mapper.registerModule(new SimpleModule() { @Override public void setupModule(SetupContext context) { super.setupModule(context); context.addBeanDeserializerModifier(new GlobalMergeableModifier()); } }); // Perform the update Person result = mapper.updateValue(destPerson, sourcePerson);
What This Does
Let's validate with your example:
- Source:
homePhoneNumber="123",address.street="Street"(other fields null) - Destination:
officePhoneNumber="456",homePhoneNumber="123",address.street="Street2",address.houseNumber="246"
After the update:
homePhoneNumberremains"123"(source matches destination, no change)officePhoneNumberstays"456"(source has null, skipped)address.streetis updated to"Street"(source has non-null, merged)address.houseNumberremains"246"(source has null, skipped—no overwrite)- The
Addressobject is merged, not replaced entirely
Why This Works
- The
GlobalMergeableModifiertells Jackson to merge nested beans instead of replacing them, eliminating the need to configure each class individually. Nulls.SKIPensures that any null properties in the source don't overwrite existing non-null values in the destination—even when merging nested objects.
内容的提问来源于stack exchange,提问作者Siddharth Trikha

