基于动态字段列表的Java对象指定字段动态序列化实现方案咨询
解决方案:基于动态字段列表序列化Java对象(Jackson/Gson实现)
针对你提出的动态字段序列化需求,我推荐使用Jackson的内置过滤器机制(结合Mixin避免修改原模型),这是性能最优且完全符合所有需求的方案;同时也提供Gson的实现作为备选。
一、Jackson实现方案(推荐)
Jackson提供了@JsonFilter和FilterProvider机制,可以动态控制序列化时的字段包含/排除。核心是用Mixin给原模型类添加过滤器注解,完全不需要修改你的ModelA/ModelB/ModelC代码。
步骤1:创建Mixin类(无需修改原模型)
Mixin是Jackson的特性,允许你在不修改目标类的情况下注入注解:
// 给ModelA绑定过滤器的Mixin @JsonFilter("dynamicIncludeFilter") public abstract class ModelAMixin {} // 给ModelB绑定过滤器的Mixin @JsonFilter("dynamicIncludeFilter") public abstract class ModelBMixin {} // 给ModelC绑定过滤器的Mixin @JsonFilter("dynamicIncludeFilter") public abstract class ModelCMixin {}
步骤2:实现动态包含过滤器
自定义过滤器,根据传入的字段列表判断哪些字段(包括嵌套对象的字段)需要保留:
import com.fasterxml.jackson.databind.ser.SimpleBeanPropertyFilter; import com.fasterxml.jackson.databind.ser.PropertyWriter; import java.util.Set; public class DynamicIncludeFilter extends SimpleBeanPropertyFilter { private final Set<String> includedFields; public DynamicIncludeFilter(Set<String> includedFields) { this.includedFields = includedFields; } @Override protected boolean include(PropertyWriter writer) { String fieldName = writer.getName(); // 两种情况保留字段: // 1. 字段名直接在包含列表中 // 2. 该字段是嵌套对象,且其下有子字段在包含列表中(比如fieldAC对应fieldBA/fieldBB) return includedFields.contains(fieldName) || includedFields.stream().anyMatch(f -> f.startsWith(fieldName + ".")); } }
步骤3:编写序列化工具方法
配置ObjectMapper,注册Mixin并应用动态过滤器:
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ser.FilterProvider; import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider; import java.util.HashSet; import java.util.List; import java.util.Set; public class JacksonSerializerUtil { public static String serializeModelA(ModelA modelA, List<String> includeFields) { ObjectMapper mapper = new ObjectMapper(); // 注册Mixin,让原模型类拥有JsonFilter注解 mapper.addMixIn(ModelA.class, ModelAMixin.class); mapper.addMixIn(ModelB.class, ModelBMixin.class); mapper.addMixIn(ModelC.class, ModelCMixin.class); Set<String> includedSet = new HashSet<>(includeFields); DynamicIncludeFilter filter = new DynamicIncludeFilter(includedSet); FilterProvider filterProvider = new SimpleFilterProvider() .addFilter("dynamicIncludeFilter", filter); try { return mapper.writer(filterProvider).writeValueAsString(modelA); } catch (Exception e) { throw new RuntimeException("序列化失败", e); } } }
使用示例
public static void main(String[] args) { // 构造测试数据 ModelB modelB = new ModelB(); modelB.setFieldBA("Value 2"); modelB.setFieldBB("Value 3"); modelB.setFieldBC("Ignored Value"); ModelC modelC = new ModelC(); modelC.setFieldCA("Ignored Value"); modelC.setFieldCB("Value 4"); ModelA modelA = new ModelA(); modelA.setFieldAA("Value 1"); modelA.setFieldAB("Ignored Value"); modelA.setFieldAC(modelB); modelA.setFieldAD(modelC); // 动态字段列表 List<String> includeFields = List.of("fieldAA", "fieldBA", "fieldBB", "fieldCB"); // 序列化 String json = JacksonSerializerUtil.serializeModelA(modelA, includeFields); System.out.println(json); }
输出结果完全符合你的期望:
{ "fieldAA": "Value 1", "fieldAC": { "fieldBA": "Value 2", "fieldBB": "Value 3" }, "fieldAD": { "fieldCB": "Value 4" } }
二、Gson实现方案(备选)
如果你的项目使用Gson,可以通过自定义ExclusionStrategy和TypeAdapterFactory实现动态字段过滤:
步骤1:实现动态排除策略
import com.google.gson.ExclusionStrategy; import com.google.gson.FieldAttributes; import java.util.Set; import java.util.concurrent.ThreadLocal; public class DynamicIncludeStrategy implements ExclusionStrategy { private final Set<String> includedFields; private final ThreadLocal<String> currentPath = ThreadLocal.withInitial(() -> ""); public DynamicIncludeStrategy(Set<String> includedFields) { this.includedFields = includedFields; } @Override public boolean shouldSkipField(FieldAttributes f) { String fieldName = f.getName(); String fullPath = currentPath.get().isEmpty() ? fieldName : currentPath.get() + "." + fieldName; // 判断当前字段是否需要保留 boolean isIncluded = includedFields.contains(fullPath) || includedFields.stream().anyMatch(f -> f.startsWith(fullPath + ".")); return !isIncluded; } @Override public boolean shouldSkipClass(Class<?> clazz) { return false; } // 用于嵌套对象序列化时更新路径 public void setCurrentPath(String path) { currentPath.set(path); } }
步骤2:实现自定义TypeAdapterFactory
处理嵌套对象的路径传递:
import com.google.gson.Gson; import com.google.gson.TypeAdapter; import com.google.gson.TypeAdapterFactory; import com.google.gson.reflect.TypeToken; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonWriter; import java.io.IOException; import java.lang.reflect.Field; public class DynamicTypeAdapterFactory implements TypeAdapterFactory { private final DynamicIncludeStrategy strategy; public DynamicTypeAdapterFactory(DynamicIncludeStrategy strategy) { this.strategy = strategy; } @Override @SuppressWarnings("unchecked") public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) { TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type); return new TypeAdapter<T>() { @Override public void write(JsonWriter out, T value) throws IOException { out.beginObject(); Field[] fields = value.getClass().getDeclaredFields(); for (Field field : fields) { field.setAccessible(true); String fieldName = field.getName(); if (!strategy.shouldSkipField(new FieldAttributes(field))) { out.name(fieldName); Object fieldValue = field.get(value); if (fieldValue != null && !isBasicType(field.getType())) { // 处理嵌套对象,更新当前路径 String prevPath = strategy.currentPath.get(); String newPath = prevPath.isEmpty() ? fieldName : prevPath + "." + fieldName; strategy.setCurrentPath(newPath); gson.toJson(fieldValue, field.getType(), out); strategy.setCurrentPath(prevPath); } else { gson.toJson(fieldValue, field.getType(), out); } } } out.endObject(); } @Override public T read(JsonReader in) throws IOException { return delegate.read(in); } // 判断是否是基础类型(不需要嵌套处理) private boolean isBasicType(Class<?> clazz) { return clazz.isPrimitive() || clazz.equals(String.class) || Number.class.isAssignableFrom(clazz) || Boolean.class.equals(clazz); } }; } }
步骤3:序列化工具方法
import com.google.gson.Gson; import com.google.gson.GsonBuilder; import java.util.HashSet; import java.util.List; import java.util.Set; public class GsonSerializerUtil { public static String serializeModelA(ModelA modelA, List<String> includeFields) { Set<String> includedSet = new HashSet<>(includeFields); DynamicIncludeStrategy strategy = new DynamicIncludeStrategy(includedSet); Gson gson = new GsonBuilder() .setExclusionStrategies(strategy) .registerTypeAdapterFactory(new DynamicTypeAdapterFactory(strategy)) .create(); return gson.toJson(modelA); } }
方案优势总结
- 完全符合需求:
- 动态支持任意字段列表,无需创建大量DTO
- 不修改原模型类的注解或访问修饰符
- 性能优秀:Jackson方案基于内置机制,开销极小;Gson方案性能略逊但也满足大多数场景
- 扩展性强:可以轻松扩展支持更复杂的字段规则(比如通配符、正则匹配)
内容的提问来源于stack exchange,提问作者Parth Prajapati
相关产品推荐
相关产品推荐

