jackson: игнорировать геттер, но не с @JsonView

Я ищу возможность сериализовать переходную информацию только в некоторых случаях:

@JsonInclude(Include.NON_NULL)
@Entity
public class User {

    public static interface AdminView {}

    ... id, email and others ...

    @Transient
    private transient Details details;

    @JsonIgnore                  // Goal: ignore all the time, except next line
    @JsonView(AdminView.class)   // Goal: don't ignore in AdminView
    public Details getDetails() {
        if (details == null) {
            details = ... compute Details ...
        }
        return details;
    }
}

public class UserDetailsAction {
    private static final ObjectWriter writer = new ObjectMapper();
    private static final ObjectWriter writerAdmin = writer
        .writerWithView(User.AdminView.class);

    public String getUserAsJson(User user) {
        return writer.writeValueAsString(user);
    }

    public String getUserAsJsonForAdmin(User user) {
        return writerAdmin.writeValueAsString(user);
    }
}

Если я вызову getUserAsJson, я ожидал увидеть идентификатор, адрес электронной почты и другие поля, но не подробности. Это прекрасно работает. Но я вижу то же самое для getUserAsJsonForAdmin, тоже без подробностей. Если я удалю аннотацию @JsonIgnore, я увижу детали в обоих вызовах.

Что я не так и есть ли хороший способ пойти? Спасибо!


person Anton Bessonov    schedule 19.04.2014    source источник


Ответы (2)


Использование динамической фильтрации Джексона может оказаться несколько более элегантным для вашего случая использования. Вот пример фильтрации полей POJO на основе пользовательской аннотации, использующей один экземпляр сопоставления объектов:

public class JacksonFilter {
    static private boolean shouldIncludeAllFields;

    @Retention(RetentionPolicy.RUNTIME)
    public static @interface Admin {}

    @JsonFilter("admin-filter")
    public static class User {
        public final String email;
        @Admin
        public final String details;

        public User(String email, String details) {
            this.email = email;
            this.details = details;
        }
    }

    public static class AdminPropertyFilter extends SimpleBeanPropertyFilter {

        @Override
        protected boolean include(BeanPropertyWriter writer) {
            // deprecated since 2.3
            return true;
        }

        @Override
        protected boolean include(PropertyWriter writer) {
            if (writer instanceof BeanPropertyWriter) {
                return shouldIncludeAllFields || ((BeanPropertyWriter) writer).getAnnotation(Admin.class) == null;
            }
            return true;
        }
    }

    public static void main(String[] args) throws JsonProcessingException {
        User user = new User("email", "secret");
        ObjectMapper mapper = new ObjectMapper();
        mapper.setFilters(new SimpleFilterProvider().addFilter("admin-filter", new AdminPropertyFilter()));
        System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(user));
        shouldIncludeAllFields = true;
        System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(user));
    }

}

Выход:

{
  "email" : "email"
}
{
  "email" : "email",
  "details" : "secret"
}
person Alexey Gavrilov    schedule 19.04.2014
comment
Хорошо, после тестирования различных решений, пользовательский фильтр и FilterIntrospector, похоже, являются тем способом, которым я хочу идти. - person Anton Bessonov; 26.04.2014
comment
Для всех, кто использует этот код в нескольких потоках, существует потенциальная проблема параллелизма при использовании shouldIncludeAllFields. Но это легко исправить, просто создав 2 ObjectMappers, 1 с фильтром и 1 без. ObjectMappers являются потокобезопасными - person muttonUp; 09.09.2015

Похоже, у Джексона ужасная концепция такой классной функции, как @JsonView. Единственный способ, который я обнаружил, чтобы решить мою проблему:

@JsonInclude(Include.NON_NULL)
@Entity
public class User {

    public static interface BasicView {}
    public static interface AdminView {}

    ... id and others ...

    @JsonView({BasicView.class, AdminView.class}) // And this for EVERY field
    @Column
    private String email;

    @Transient
    private transient Details details;

    @JsonView(AdminView.class)
    public Details getDetails() {
        if (details == null) {
            details = ... compute Details ...
        }
        return details;
    }
}

public class UserDetailsAction {
    private static final ObjectWriter writer = new ObjectMapper()
        .disable(MapperFeature.DEFAULT_VIEW_INCLUSION)
        .writerWithView(User.BasicView.class);
    private static final ObjectWriter writerAdmin = new ObjectMapper()
        .disable(MapperFeature.DEFAULT_VIEW_INCLUSION)
        .writerWithView(User.AdminView.class);

    public String getUserAsJson(User user) {
        return writer.writeValueAsString(user);
    }

    public String getUserAsJsonForAdmin(User user) {
        return writerAdmin.writeValueAsString(user);
    }
}

Может быть, это поможет кому-то. Но я надеюсь найти лучшее решение, потому что не принимаю свой собственный ответ.

EDIT: поскольку интерфейс может расширять (несколько) интерфейсов, я могу использовать:

public static interface AdminView extends BasicView {}

и просто

@JsonView(BasicView.class)

вместо

@JsonView({BasicView.class, AdminView.class})
person Anton Bessonov    schedule 19.04.2014
comment
Как мне это сделать для POJO, который отправляется как ответ REST? Примечание. Я не использую ObjectWriter. - person Chaman; 05.10.2018
comment
поможет ли использование null вместо какого-либо конкретного представления? @JsonView(null) // Цель: игнорировать все время, кроме следующей строки - person Ilya Yevlampiev; 12.08.2019