如何根据杰克逊的运行时条件将集合序列化为空列表

时间:2019-02-17 09:02:42

标签: java spring jackson aop

我们有一项业务要求,如果用户没有查看子实体的权限,则在spring boot应用程序中实体子集合(我们使用JPA)的元素不应在rest api中可见。

现在,我们使用AOP将所有get方法包装到服务中,以便它们执行类似if (!allowed("ChildView")) {entity.setChildren(new ArrayList<>())}的操作,由于某些原因,这对我来说似乎不是一个好的解决方案。首先,权限名称和集合设置器之间的关系是在实体外部进行硬编码的。还要修改实际对象,因为我们不想在REST API中显示有关它的内容,这似乎有些奇怪。如果您不想显示某些内容,则不要删除它。您可以将其隐藏。所以我想为什么在序列化时不隐藏它?

所以我可以看到如何在运行时通过Mixin@JsonIgnore完全忽略属性,但是我找不到如何返回空列表的方法。

理想情况下,我喜欢这样的API。

class Entity {
    @OneToMany
    @AuthSerialize("ChildView", default=Collections.emptyList())
    Collection<Child> children;
}

当前解决方案看起来像这样。

Map<Class<? extends BaseEntity>, Map<String, Consumer<BaseEntity>> protectors;

process(BaseEntity e) {
    protectors.getOrDefault(e.getClass(), Collectoions.emptyMap())).forEach((permission, clearer) ->
        if !allowed(permission) clearer.accept(e)
    )

2 个答案:

答案 0 :(得分:1)

我认为“不浪费周期”是过度设计。如果您每秒要序列化一百万个实体,那么这可能是一个有效的断言。否则,JVM将为您优化“热点”。而且无论如何,这不会成为您应用程序体系结构的瓶颈。

如果您知道您的实体有一个共同的“子级”数组字段,您可能希望通过维护兼容类中的JsonSerializer来将相同的Map应用于所有它们。 / p>

您必须了解杰克逊有其自身的局限性。如果您需要的还不止这些,则可能需要完全定制的解决方案。这是您可以从杰克逊获得的最好的东西。


希望答案令人满意。
您可以使用自定义的JsonSerializer<T>

class EntitySerializer extends StdSerializer<Entity> {
    private static final long serialVersionUID = 1L;
    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    EntitySerializer() {
        super(Entity.class);
    }

    @Override
    public void serialize(
            final Entity value,
            final JsonGenerator generator,
            final SerializerProvider provider) throws IOException {
        final TreeNode jsonNode = OBJECT_MAPPER.valueToTree(value);

        if (!AuthUtils.allowed("ChildView")) {
            final TreeNode children = jsonNode.get("children");

            if (children.isArray()) {
                ((ContainerNode<ArrayNode>) children).removeAll();
            }
        }

        generator.writeTree(jsonNode);
    }
}

但是,正如您所看到的,我们正在ObjectMapper内使用JsonSerializer实例(或者您希望使用JsonGenerator手动“写”每个字段吗?我不这么认为:P)。由于ObjectMapper寻找注释,为避免序列化过程的无限递归,您必须放弃类注释

@JsonSerialize(using = EntitySerializer.class) 

然后将自定义JsonSerializer手动注册到Jackson ObjectMapper

final SimpleModule module = new SimpleModule();
module.setSerializerModifier(new BeanSerializerModifier() {
    @Override
    public JsonSerializer<?> modifySerializer(
            final SerializationConfig config,
            final BeanDescription beanDesc,
            final JsonSerializer<?> serializer) {
        final Class<?> beanClass = beanDesc.getBeanClass();
        return beanClass == Entity.class ? new EntitySerializer() : serializer;
    }
});

final ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(module);

最后,您只需要使用ObjectMapper,或让您的框架使用它。
使用Spring时,可以注册标记为@Bean的ObjectMapper类型的@Primary,也可以注册@Bean类型的Jackson2ObjectMapperBuilder


上一个答案。

由于allowed方法是静态的,这意味着可以从“无处不在”访问它。 与杰克逊玩了一会儿之后,我将为您提供两个选项中的第一个,因为我仍在研究第二个选项。

注释您的班级
@JsonSerialize(converter = EntityConverter.class)
public class Entity { ... }

这里您要指定自定义Converter

Converter实现非常简洁。
在静态块内,我只是获得了Auth批注值,但这是可选的,您可以根据自己的用例来做最合适的事情。

class EntityConverter extends StdConverter<Entity, Entity> {
    private static final String AUTH_VALUE;

    static {
        final String value;

        try {
            final Field children = Entity.class.getDeclaredField("children");
            final AuthSerialize auth = children.getAnnotation(AuthSerialize.class);
            value = auth != null ? auth.value() : null;
        } catch (final NoSuchFieldException e) {
            // Provide appropriate Exception, or handle it
            throw new RuntimeException(e);
        }

        AUTH_VALUE = value;
    }

    @Override
    public Entity convert(final Entity value) {
        if (AUTH_VALUE != null) {
            if (!AuthUtils.allowed(AUTH_VALUE)) {
                value.children.clear();
            }
        }

        return value;
    }
}

让我知道这是否足够,或者您希望使用更复杂的解决方案。

答案 1 :(得分:0)

您可以使用Mixin覆盖getter方法:

class noChildViewEntity {

    public Collection<Child> getChildren() {
        return new ArrayList<>();
    }

}