我使用的是Spring 4.3.3版本和Jackson版本2.8.3。我试图根据在运行时确定的一些自定义逻辑从实体bean中过滤掉特定字段。 @JsonFilter似乎是这类功能的理想选择。问题是当我把它放在字段或方法级别时,我的自定义过滤器永远不会被调用。如果我把它放在类级别,它会被调用就好了。我不想在类级别使用它,但从那时起我需要单独维护我想要应用逻辑的硬编码字段名称列表。从Jackson 2.3开始,应该存在将这个注释放在字段级别的能力。
这是最基本的自定义过滤器,没有任何自定义逻辑:
public class MyFilter extends SimpleBeanPropertyFilter {
@Override
protected boolean include(BeanPropertyWriter beanPropertyWriter) {
return true;
}
@Override
protected boolean include(PropertyWriter propertyWriter) {
return true;
}
}
然后我有Jackson ObjectMapper配置:
public class MyObjectMapper extends ObjectMapper {
public MyObjectMapper () {
SimpleFilterProvider filterProvider = new SimpleFilterProvider();
filterProvider.addFilter("myFilter", new MyFilter());
setFilterProvider(filterProvider);
}
}
然后我终于拥有了我的实体bean:
@Entity
public class Project implements Serializable {
private Long id;
private Long version;
@JsonFilter("myFilter") private String name;
@JsonFilter("myFilter") private String description;
// getters and setters
}
如果我将@JsonFilter注释移动到@Entity所在的类级别,则至少会调用过滤器,但是当它处于字段级别时(如此处的示例所示),它永远不会被调用。
答案 0 :(得分:4)
我有同样的需求,但在检查单元测试后,我发现这不是注释字段所涵盖的用例。
注释字段会调用字段值的过滤器,而不是包含字段的实例。例如,假设您必须使用类A和B,其中A包含B类型的字段。
class A {
@JsonFilter("myFilter") B foo;
}
Jackson将“myFilter”应用于B中不在A中的字段。由于您的示例包含String类型的字段,没有字段,因此Jackson永远不会调用您的过滤器。
答案 1 :(得分:0)
你可以尝试这种方法用于同样的目的:
@Entity
@Inheritance(
strategy = InheritanceType.SINGLE_TABLE
)
@DiscriminatorColumn(
discriminatorType = DiscriminatorType.STRING,
length = 2
)
@Table(
name = "project"
)
@JsonTypeInfo(
use = Id.CLASS,
include = As.PROPERTY,
property = "@class"
)
@JsonSubTypes({
@Type(
value = BasicProject.class,
name = "basicProject"
),
@Type(
value = AdvanceProject.class,
name = "advanceProject"
)})
public abstract class Project {
private Long id;
private Long version;
}
@Entity
@DiscriminatorValue("AD")
public class AdvanceProject extends Project {
private String name;
private String description;
}
@Entity
@DiscriminatorValue("BS")
public class BasicProject extends Project {
private String name;
}
答案 2 :(得分:0)
我认为你不会让它发挥作用。我正在尝试,这些都是我调查的结果,也许会有所帮助。
首先,正如@Faron所注意到的,@JsonFilter
注释适用于注释的类而不是字段。
其次,我以这种方式看待事物。让我们想象一下,在杰克逊内部的某个地方你可以得到真正的领域。您可以使用Java Reflection API确定是否存在注释。您甚至可以获取过滤器名称。然后你到达过滤器并在那里传递字段值。但它发生在运行时,如果您决定序列化字段,您将如何获得字段类型的相应JsonSerializer
?由于类型擦除,这是不可能的。
我看到的唯一选择是忘记动态逻辑。然后你可以做以下事情:
1)扩展JacksonAnnotationIntrospector
(几乎与实现AnnotationIntrospector
相同,但没有无用的默认代码)覆盖hasIgnoreMarker
方法。看看this answer
2)罪犯从这里开始。有点奇怪的方式考虑到你的初始目标,但仍然:扩展BeanSerializerModifier
并过滤掉那里的字段。可以找到一个示例here。通过这种方式,您可以定义实际上不会序列化任何内容的序列化程序(同样,我知道它有多奇怪,但也许会发现它有用)
3)与上述方法类似:根据BeanDescription
实现ContextualSerializer
的{{1}}方法定义无用的序列化程序。这个魔法的例子是here
答案 3 :(得分:0)
我需要根据来电者的权限排除某些字段。例如,员工的个人资料可能包含他的纳税人ID,该ID被视为敏感信息,并且只有在呼叫者是Payrole部门的成员时才应序列化。由于我使用的是Spring Security,我希望将Jackson与当前的安全环境集成在一起。
public class EmployeeProfile {
private String givenName;
private String surname;
private String emailAddress;
@VisibleWhen("hasRole('PayroleSpecialist')")
private String taxpayerId;
}
最明显的做法是杰克逊的过滤机制,但它有一些局限性:
对于我的用例,解决方案是使用自定义注释introspector和链接过滤器。
public class VisibilityAnnotationIntrospector extends JacksonAnnotationIntrospector {
private static final long serialVersionUID = 1L;
@Override
public Object findFilterId(Annotated a) {
Object result = super.findFilterId(a);
if (null != result) return result;
// By always returning a value, we cause Jackson to query the filter provider.
// A more sophisticated solution will introspect the annotated class and only
// return a value if the class contains annotated properties.
return a instanceof AnnotatedClass ? VisibilityFilterProvider.FILTER_ID : null;
}
}
这基本上是一个SimpleBeanProvider副本,它通过调用include
来取代对isVisible
的调用。我可能会更新这个以使用Java 8 BiPredicate来使解决方案更通用但现在可以使用。
这个类还将另一个过滤器作为参数,如果字段可见,将委托给它最终决定是否序列化字段。
public class AuthorizationFilter extends SimpleBeanPropertyFilter {
private final PropertyFilter antecedent;
public AuthorizationFilter() {
this(null);
}
public AuthorizationFilter(final PropertyFilter filter) {
this.antecedent = null != filter ? filter : serializeAll();
}
@Deprecated
@Override
public void serializeAsField(Object bean, JsonGenerator jgen, SerializerProvider provider, BeanPropertyWriter writer) throws Exception {
if (isVisible(bean, writer)) {
this.antecedent.serializeAsField(bean, jgen, provider, writer);
} else if (!jgen.canOmitFields()) { // since 2.3
writer.serializeAsOmittedField(bean, jgen, provider);
}
}
@Override
public void serializeAsField(Object pojo, JsonGenerator jgen, SerializerProvider provider, PropertyWriter writer) throws Exception {
if (isVisible(pojo, writer)) {
this.antecedent.serializeAsField(pojo, jgen, provider, writer);
} else if (!jgen.canOmitFields()) { // since 2.3
writer.serializeAsOmittedField(pojo, jgen, provider);
}
}
@Override
public void serializeAsElement(Object elementValue, JsonGenerator jgen, SerializerProvider provider, PropertyWriter writer) throws Exception {
if (isVisible(elementValue, writer)) {
this.antecedent.serializeAsElement(elementValue, jgen, provider, writer);
}
}
private static boolean isVisible(Object pojo, PropertyWriter writer) {
// Code to determine if the field should be serialized.
}
}
然后我为每个ObjectMapper实例添加一个自定义过滤器提供程序。
@SuppressWarnings("deprecation")
public class VisibilityFilterProvider extends SimpleFilterProvider {
private static final long serialVersionUID = 1L;
static final String FILTER_ID = "dummy-filter-id";
@Override
public BeanPropertyFilter findFilter(Object filterId) {
return super.findFilter(filterId);
}
@Override
public PropertyFilter findPropertyFilter(Object filterId, Object valueToFilter) {
if (FILTER_ID.equals(filterId)) {
// This implies that the class did not have an explict filter annotation.
return new AuthorizationFilter(null);
}
// The class has an explicit filter annotation so delegate to it.
final PropertyFilter antecedent = super.findPropertyFilter(filterId, valueToFilter);
return new VisibilityPropertyFilter(antecedent);
}
}
最后,我有一个Jackson模块可以自动注册自定义注释内部跟踪器,因此我不必手动将它添加到每个ObjectMapper实例中。
public class FieldVisibilityModule extends SimpleModule {
private static final long serialVersionUID = 1L;
public FieldVisibilityModule() {
super(PackageVersion.VERSION);
}
@Override
public void setupModule(Module.SetupContext context) {
super.setupModule(context);
// Append after other introspectors (instead of before) since
// explicit annotations should have precedence
context.appendAnnotationIntrospector(new VisibilityAnnotationIntrospector());
}
}
可以进行更多改进,我仍然需要编写更多单元测试(例如,处理数组和集合),但这是我使用的基本策略。
答案 4 :(得分:0)
感谢blog的出色表现,我能够使用@JsonView
根据运行时确定的一些自定义逻辑从实体bean中过滤出特定字段。
由于@JsonFilter
不适用于类中的字段,因此我发现这是一种更清洁的解决方法。
这是示例代码:
@Data
@AllArgsConstructor
public class TestEntity {
private String a;
@JsonView(CustomViews.SecureAccess.class)
private Date b;
@JsonView(CustomViews.SecureAccess.class)
private Integer c;
private List<String> d;
}
public class CustomViews {
public static interface GeneralAccess {}
public static interface SecureAccess {}
public static class GeneralAccessClass implements GeneralAccess {}
public static class SecureAccessClass implements SecureAccess, GeneralAccess {}
public static Class getWriterView(final boolean hasSecureAccess) {
return hasSecureAccess
? SecureAccessClass.class
: GeneralAccessClass.class;
}
}
@Test
public void test() throws JsonProcessingException {
final boolean hasSecureAccess = false; // Custom logic resolved to a boolean value at runtime.
final TestEntity testEntity = new TestEntity("1", new Date(), 2, ImmutableList.of("3", "4", "5"));
final ObjectMapper objectMapper = new ObjectMapper().enable(MapperFeature.DEFAULT_VIEW_INCLUSION);
final String serializedValue = objectMapper
.writerWithView(CustomViews.getWriterView(hasSecureAccess))
.writeValueAsString(testEntity);
Assert.assertTrue(serializedValue.contains("a"));
Assert.assertFalse(serializedValue.contains("b"));
Assert.assertFalse(serializedValue.contains("c"));
Assert.assertTrue(serializedValue.contains("d"));
}