如何基于嵌套标识符配置Gson的字段包含与排除策略?
我来帮你解决这个Gson序列化字段精准控制的问题,这确实是默认排除策略的一个痛点——按字段名排除会影响所有类的同名字段。下面针对你的两个需求,以及嵌套路径的场景给出具体实现方案:
解决方案:基于类+字段路径的Gson字段控制
一、核心思路
Gson默认的ExclusionStrategy只能按字段名或类来排除,无法精准定位到特定类的特定字段或嵌套路径下的字段。我们需要自定义扩展ExclusionStrategy或结合TypeAdapterFactory来实现路径级别的字段控制。
二、需求1:仅移除B.field4,保留其余所有字段
这个需求只需要精准匹配B类的field4字段,不需要考虑嵌套路径,实现起来比较简单:
1. 自定义ExclusionStrategy
import com.google.gson.ExclusionStrategy; import com.google.gson.FieldAttributes; public class SpecificFieldExcluder implements ExclusionStrategy { private final Class<?> targetClass; private final String targetFieldName; public SpecificFieldExcluder(Class<?> targetClass, String targetFieldName) { this.targetClass = targetClass; this.targetFieldName = targetFieldName; } @Override public boolean shouldSkipField(FieldAttributes fieldAttrs) { // 仅当字段属于目标类且字段名匹配时,才排除该字段 return targetClass.isAssignableFrom(fieldAttrs.getDeclaringClass()) && targetFieldName.equals(fieldAttrs.getName()); } @Override public boolean shouldSkipClass(Class<?> clazz) { // 不排除任何类 return false; } }
2. 初始化Gson实例
Gson gson = new GsonBuilder() .setExclusionStrategies(new SpecificFieldExcluder(B.class, "field4")) .create();
这样序列化时,只有B类的field4会被排除,其他类的同名字段(如果有的话)不受影响。
三、需求2:仅保留B.field4,其余所有字段均排除
这个需求需要反过来:除了B类的field4,其他所有字段都要排除。实现如下:
1. 自定义ExclusionStrategy
import com.google.gson.ExclusionStrategy; import com.google.gson.FieldAttributes; public class OnlyKeepSpecificFieldStrategy implements ExclusionStrategy { private final Class<?> targetClass; private final String targetFieldName; public OnlyKeepSpecificFieldStrategy(Class<?> targetClass, String targetFieldName) { this.targetClass = targetClass; this.targetFieldName = targetFieldName; } @Override public boolean shouldSkipField(FieldAttributes fieldAttrs) { // 仅当字段是目标类的目标字段时,不排除;其余所有字段都排除 return !(targetClass.isAssignableFrom(fieldAttrs.getDeclaringClass()) && targetFieldName.equals(fieldAttrs.getName())); } @Override public boolean shouldSkipClass(Class<?> clazz) { return false; } }
2. 初始化Gson实例
Gson gson = new GsonBuilder() .setExclusionStrategies(new OnlyKeepSpecificFieldStrategy(B.class, "field4")) .create();
此时序列化任何对象时,只有B类的field4会被保留,其他所有字段(包括A的field1/field2、B的field3/listO等)都会被排除。
四、进阶:支持嵌套路径(如B.C.field5)
如果需要控制嵌套层级的字段(比如B类中C类型字段的field5,或者A.listB中的B.field4),仅靠ExclusionStrategy无法追踪字段的嵌套路径,需要结合TypeAdapterFactory来维护当前序列化的路径:
1. 自定义PathTrackingTypeAdapterFactory
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 com.google.gson.JsonElement; import com.google.gson.JsonObject; import java.io.IOException; import java.lang.reflect.Field; import java.util.Set; public class PathTrackingTypeAdapterFactory implements TypeAdapterFactory { private final Set<String> excludedPaths; // 用ThreadLocal维护当前序列化路径,保证线程安全 private final ThreadLocal<StringBuilder> currentPath = ThreadLocal.withInitial(StringBuilder::new); public PathTrackingTypeAdapterFactory(Set<String> excludedPaths) { this.excludedPaths = excludedPaths; } @Override @SuppressWarnings("unchecked") public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) { final TypeAdapter<T> delegateAdapter = gson.getDelegateAdapter(this, type); final TypeAdapter<JsonElement> jsonElementAdapter = gson.getAdapter(JsonElement.class); return new TypeAdapter<T>() { @Override public void write(JsonWriter out, T value) throws IOException { if (value == null) { out.nullValue(); return; } Class<?> clazz = value.getClass(); JsonObject jsonObject = delegateAdapter.toJsonTree(value).getAsJsonObject(); // 遍历当前类的所有字段 for (Field field : clazz.getDeclaredFields()) { field.setAccessible(true); String fieldName = field.getName(); String fullFieldPath = buildFullPath(clazz.getSimpleName(), fieldName); if (excludedPaths.contains(fullFieldPath)) { // 如果路径在排除列表中,移除该字段 jsonObject.remove(fieldName); } else { // 递归处理嵌套字段,更新当前路径 appendToCurrentPath(clazz.getSimpleName(), fieldName); Object fieldValue = field.get(value); if (fieldValue != null) { JsonElement nestedElement = jsonElementAdapter.toJsonTree(fieldValue); jsonObject.add(fieldName, nestedElement); } // 处理完嵌套字段后,重置路径 removeFromCurrentPath(clazz.getSimpleName(), fieldName); } } jsonElementAdapter.write(out, jsonObject); } @Override public T read(JsonReader in) throws IOException { return delegateAdapter.read(in); } // 构建完整的字段路径 private String buildFullPath(String className, String fieldName) { StringBuilder path = currentPath.get(); return path.length() == 0 ? className + "." + fieldName : path.toString() + "." + className + "." + fieldName; } // 将当前字段路径追加到线程本地的路径中 private void appendToCurrentPath(String className, String fieldName) { StringBuilder path = currentPath.get(); if (path.length() > 0) { path.append("."); } path.append(className).append(".").append(fieldName); } // 从线程本地路径中移除当前字段的路径部分 private void removeFromCurrentPath(String className, String fieldName) { StringBuilder path = currentPath.get(); String segment = "." + className + "." + fieldName; int lastIndex = path.lastIndexOf(segment); if (lastIndex != -1) { path.setLength(lastIndex); } else { path.setLength(0); } } }; } }
2. 使用示例(排除嵌套路径字段)
比如要排除B类中C类型字段c的field5,以及A类中listB字段里的B.field4:
import java.util.HashSet; import java.util.Set; Set<String> excludedPaths = new HashSet<>(); excludedPaths.add("B.c.field5"); excludedPaths.add("A.listB.field4"); Gson gson = new GsonBuilder() .registerTypeAdapterFactory(new PathTrackingTypeAdapterFactory(excludedPaths)) .create();
这样就能精准排除指定嵌套路径下的字段,不会影响其他位置的同名字段。
内容的提问来源于stack exchange,提问作者Akash Shinde
相关产品推荐
相关产品推荐

