Как сериализовать коллекцию как пустой список в зависимости от условий выполнения с Джексоном - PullRequest
0 голосов
/ 17 февраля 2019

У нас есть бизнес-требование, чтобы элементы дочерних коллекций сущностей (мы используем JPA) в нашем приложении весенней загрузки не были видны в 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 голосов
/ 17 февраля 2019

Я думаю, что "не тратить циклы" чрезмерно инженерно.Это может быть верным утверждением, если вы сериализуете миллион объектов в секунду.В противном случае JVM оптимизирует «горячую точку» для вас.И в любом случае, это не будет узким местом в архитектуре вашего приложения.

Если вы знаете, что у ваших сущностей есть общее поле «дочерний» массив, вы можете применить один и тот же JsonSerializer ко всем из них.просто присваивая Map совместимых классов.

Вы должны понимать, что у Джексона есть свои ограничения.Если вам нужно что-то большее, вам может потребоваться полностью индивидуальное решение.Это лучшее, что вы можете получить с Джексоном.


Надеюсь, что ответ удовлетворительный.
Вы можете использовать пользовательский 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;
    }
}

Дайте мне знать, достаточно ли этого, или вы бы предпочли более сложное решение.

0 голосов
/ 17 февраля 2019

Вы можете использовать Mixin для переопределения метода получения:

class noChildViewEntity {

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

}
...