Jackson @JsonFilter在现场或方法级别使用时未获得应用

时间:2016-09-29 15:45:24

标签: spring jackson

我使用的是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所在的类级别,则至少会调用过滤器,但是当它处于字段级别时(如此处的示例所示),它永远不会被调用。

5 个答案:

答案 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;
}

最明显的做法是杰克逊的过滤机制,但它有一些局限性:

  1. Jackson不支持嵌套过滤器,因此添加访问过滤器禁止将过滤器用于任何其他目的。
  2. 无法将杰克逊注释添加到现有的第三方课程中。
  3. Jackson过滤器不是通用的。目的是为要应用过滤的每个类编写自定义过滤器。例如,我需要过滤类A和B,然后你必须写一个AFilter和一个BFilter。
  4. 对于我的用例,解决方案是使用自定义注释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"));
}