繁体   English   中英

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

[英]How to serialize collection as empty list depending on runtime condition with jackson

我们有一项业务要求,即如果用户没有查看子实体的权限,则我们的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)
    )

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

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

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


希望答案令人满意。
您可以使用自定义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);
    }
}

但是,正如您所看到的,我们在JsonSerializer中使用了ObjectMapper实例(或者您希望使用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时,您可以注册一个类型为ObjectMapper的@Bean ,标记为@Primary ,也可以注册一个类型为@BeanJackson2ObjectMapperBuilder


先前的答案。

由于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;
    }
}

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

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

class noChildViewEntity {

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

}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM