Jackson能否仅反序列化接口中定义的字段?
答:默认不会自动限制,但可以通过配置/注解实现只序列化对应接口的字段
默认情况下,Jackson序列化对象时,会扫描实际实例类型的所有可访问属性(比如所有getter方法对应的字段),而不管你把实例赋值给哪个接口类型变量。举个例子,你的代码里如果直接调用mapper.writeValueAsString(implA),输出的JSON会同时包含a和b两个字段,因为Jackson会识别到Impl类的两个getter方法。
如果你想实现「以接口A类型序列化时只保留A声明的字段,以B类型序列化时只保留B的字段」,可以用下面几种方法:
方法1:使用@JsonView(最推荐,简洁直观)
@JsonView是Jackson提供的用于按视图分组序列化字段的注解,我们可以给不同接口的字段标记不同的视图,序列化时指定对应的视图即可:
首先定义视图标记类:
public class Views { public interface ViewA {} public interface ViewB {} }
然后修改你的接口和实现类,给getter方法添加对应视图注解:
public interface A { @JsonView(Views.ViewA.class) String getA(); } public interface B { @JsonView(Views.ViewB.class) String getB(); } public class Impl implements A, B { @Override @JsonView(Views.ViewA.class) public String getA() { return "stringA"; } @Override @JsonView(Views.ViewB.class) public String getB() { return "stringB"; } public static void main(String[] args) throws JsonProcessingException { ObjectMapper mapper = new ObjectMapper(); A implA = new Impl(); B implB = new Impl(); // 序列化A类型时指定ViewA,仅输出a字段 String jsonA = mapper.writerWithView(Views.ViewA.class).writeValueAsString(implA); // 输出结果: {"a":"stringA"} // 序列化B类型时指定ViewB,仅输出b字段 String jsonB = mapper.writerWithView(Views.ViewB.class).writeValueAsString(implB); // 输出结果: {"b":"stringB"} } }
方法2:使用属性过滤器SimpleBeanPropertyFilter
如果你不想修改原有代码的注解,可以通过过滤器来指定序列化时保留的字段:
public static void main(String[] args) throws JsonProcessingException { ObjectMapper mapper = new ObjectMapper(); // 针对接口A,创建过滤器只保留"a"字段 FilterProvider filterA = new SimpleFilterProvider() .addFilter("filterA", SimpleBeanPropertyFilter.filterOutAllExcept("a")); String jsonA = mapper.writer(filterA).writeValueAsString(new Impl()); // 输出: {"a":"stringA"} // 针对接口B,创建过滤器只保留"b"字段 FilterProvider filterB = new SimpleFilterProvider() .addFilter("filterB", SimpleBeanPropertyFilter.filterOutAllExcept("b")); String jsonB = mapper.writer(filterB).writeValueAsString(new Impl()); // 输出: {"b":"stringB"} }
这种方法的缺点是需要手动指定字段名,如果接口方法较多容易出错。
方法3:自定义序列化器修饰符(动态适配接口)
如果你的接口数量较多,或者需要动态根据接口类型过滤字段,可以自定义BeanSerializerModifier,通过反射自动识别接口的getter方法对应的字段:
首先定义一个用于传递目标接口的ThreadLocal工具类:
public class InterfaceHolder { private static final ThreadLocal<Class<?>> holder = new ThreadLocal<>(); public static void set(Class<?> intf) { holder.set(intf); } public static Class<?> get() { return holder.get(); } public static void remove() { holder.remove(); } }
然后实现自定义序列化器修饰符:
public class InterfaceSerializerModifier extends BeanSerializerModifier { @Override public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc, List<BeanPropertyWriter> beanProperties) { Class<?> targetInterface = InterfaceHolder.get(); if (targetInterface != null) { Set<String> allowedProps = new HashSet<>(); // 遍历接口的所有getter方法,转换为对应的字段名(比如getA() -> "a") for (Method method : targetInterface.getMethods()) { if (method.getName().startsWith("get") && method.getParameterCount() == 0) { String propName = Introspector.decapitalize(method.getName().substring(3)); allowedProps.add(propName); } } // 过滤掉不在允许列表中的属性 beanProperties.removeIf(writer -> !allowedProps.contains(writer.getName())); } return super.changeProperties(config, beanDesc, beanProperties); } }
最后在主方法中使用:
public static void main(String[] args) throws JsonProcessingException { ObjectMapper mapper = new ObjectMapper(); // 注册自定义序列化器修饰符 mapper.setSerializerFactory(mapper.getSerializerFactory().withSerializerModifier(new InterfaceSerializerModifier())); A implA = new Impl(); InterfaceHolder.set(A.class); String jsonA = mapper.writeValueAsString(implA); InterfaceHolder.remove(); // 输出: {"a":"stringA"} B implB = new Impl(); InterfaceHolder.set(B.class); String jsonB = mapper.writeValueAsString(implB); InterfaceHolder.remove(); // 输出: {"b":"stringB"} }
这种方法的优势是不需要修改原有接口和实现类的代码,能自动适配任意接口类型,但实现相对复杂一些。
内容的提问来源于stack exchange,提问作者Kev
相关产品推荐
相关产品推荐

